To compare the performance of varKode to Skmer, we will use leave-one-out cross validation: we remove one sample from the dataset, train a varKode model or make a skmer reference with the remaining samples, and then use the sample left out as query. We then record whether or not we correctly identify this sample in varKoder, and whether or not the closest sample with Skmer has the same identification.

For traditional barcodes, we assembled the genome of each sample, and then used BLAST to search for each of the traditional barcode genes. We recorded if we could find this gene in the assembly, coding as missing data if we could not. We then recorded whether the best BLAST hit for a sample was the correct species.

rm(list=ls())
library(tidyverse)
library(future)
library(ggthemes)
library(patchwork)
library(cowplot)
library(patchwork)
library(phytools)
library(ape)
set.seed(14164)

VarKoder

For VarKoder, we used leave-one-out cross-validation to test the accuracy for family, genera, species in the joint Malpighiaceae-Chrysobalanaceae dataset. We used as input data varKodes produced from kmers of size 7 and 500Kbp to 200Mbp of data, or all of the data available if less than 200 Mbp. For each sample, we built a model using as input data from all other samples. Then we queried the sample left out, using as input the images generated from 500Kb to the total data available. Now we will summarize the results.

Accuracy vs data amount and taxonomic levels

In this test, we used varKoder v0.6.0. Let’s process the results.

read_and_process_xval = function(infolder){
  plan(multisession(workers = 12))
varkoder_results = list.files(infolder,
                                      'predictions.csv',
                                      recursive=T,
                                      full.names = T) %>%
  furrr::future_map_dfr(~read_csv(.x) %>% mutate(sample_id = as.character(sample_id))) %>% 
  select(-1) %>%
  filter(str_detect(query_basepairs,'^0+[125]0+K$')) %>% #we will ignore queries that are not standardized sizes
  rename(query_bp = query_basepairs) %>%
  mutate(quality_included = T)
plan(sequential)

all_taxlabels = str_remove(varkoder_results$actual_labels,";*low_quality:True;*") %>% str_split(';') %>% unlist %>% unique

varkoder_results = varkoder_results %>%
  mutate(query_labels = str_remove(actual_labels,";*low_quality:True;*") %>% str_split(';'),
         predicted_list = str_split(predicted_labels,';')
         ) %>%
  rowwise() %>%
  mutate(family_correct = query_labels[str_detect(query_labels,'family')] %in% predicted_list,
         genus_correct = query_labels[str_detect(query_labels,'genus')] %in% predicted_list,
         species_correct = ifelse(any(str_detect(query_labels,'species')),
                                  query_labels[str_detect(query_labels,'species')] %in% predicted_list,
                                  NA
                                  ),
         family_incorrect = any(!(predicted_list[str_detect(predicted_list,'family')] %in% query_labels[str_detect(query_labels,'family')])),
         genus_incorrect = any(!(predicted_list[str_detect(predicted_list,'genus')] %in% query_labels[str_detect(query_labels,'genus')])),
         species_incorrect = ifelse(any(str_detect(query_labels,'species')),
                                  any(!(predicted_list[str_detect(predicted_list,'species')] %in% query_labels[str_detect(query_labels,'species')])),
                                  NA
                                  )
         
         )

return(varkoder_results)
}
summarize_results = function(res,level){
  res = res %>%
    ungroup() %>%
    mutate(low_quality = str_detect(actual_labels,"low_quality:True"),
           result = as.character(ifelse(res[,str_c(level,'correct',sep='_')] & !res[,str_c(level,'incorrect',sep='_')], 'correct',
                           ifelse(res[,str_c(level,'correct',sep='_')] & res[,str_c(level,'incorrect',sep='_')], 'ambiguous',
                                  ifelse(!res[,str_c(level,'correct',sep='_')]  & res[,str_c(level,'incorrect',sep='_')], 'incorrect',
                                                 'inconclusive'
                                  ))))
           ) %>%
    filter(!is.na(result)) %>%
    group_by(query_bp,result) %>%
    summarise(N=n(), .groups = 'drop') %>%
    group_by(query_bp) %>%
    mutate(p= N/sum(N)) %>%
    mutate(query_bp = as.integer(str_remove(query_bp,'K'))*1000) %>%
    ungroup() %>%
    mutate(query_bp = as.factor(query_bp)) %>%
    complete(query_bp,result, fill = list(p = 0, N = 0)) %>%
    mutate(query_bp = as.numeric(as.character(query_bp))) %>%
    ungroup()
    
  return(res)
}
plot_area = function(sum_df, title, relative = FALSE, grid = TRUE, xlim_all = TRUE, wrap){
  breaks = c(500000,
             1000000,
             2000000,
             5000000,
             10000000,
             20000000,
             50000000,
             100000000,
             200000000
             )
  if (xlim_all){
    xlimits = range(breaks)
  } else {
    xlimits = range(sum_df$query_bp)
  }
  
  
  sum_df = sum_df %>%
    mutate(result = factor(result,ordered = T, levels = c('correct','ambiguous','inconclusive','incorrect'))) 
  if (relative){
    ylimits = c(0,1)
  } else {
    ylimits = c(0,sum_df %>% group_by(query_bp) %>% summarize(N=sum(N)) %>% pull(N) %>% max)
  }
  
  
  # Get colors from a Color Brewer palette
  brewer_colors <- RColorBrewer::brewer.pal(4, "Accent")
  
  if (relative) {
    p1 = ggplot(sum_df, aes(x=query_bp,y=p,fill=result)) +
    geom_area(position='stack') +
    scale_fill_manual(values = setNames(brewer_colors, c("correct", "ambiguous", "inconclusive", "incorrect"))) +
    scale_alpha_manual(values=c(0.5,1)) +
    scale_x_log10(labels = scales::label_number(scale_cut = scales::cut_si('bp')),breaks = breaks)  +
    scale_y_continuous() +
    ggtitle(title) +
    ylab('Fraction of samples') +
    xlab('Base pairs in query images') +
    theme_few() +
    theme(axis.text.x = element_text(hjust=1,angle=45))
  } else {
      p1 = ggplot(sum_df, aes(x=query_bp,y=N,fill=result)) +
    geom_area(position='stack') +
    scale_fill_manual(values = setNames(brewer_colors, c("correct", "ambiguous", "inconclusive", "incorrect"))) +
    scale_alpha_manual(values=c(0.5,1)) +
    scale_x_log10(labels = scales::label_number(scale_cut = scales::cut_si('bp')),breaks = breaks)   +
    scale_y_continuous() +
    ggtitle(title) +
    ylab('Number of samples') +
    xlab('Base pairs in query images') +
    theme_few() +
    theme(axis.text.x = element_text(hjust=1,angle=45))
  }
  
  if (grid){
    p1 = p1 +
      scale_y_continuous(n.breaks = 10, minor_breaks = waiver()) +
      theme(panel.background = element_rect(fill = NA),
            panel.grid.major.y = element_line(colour = gray(0.5)),
            panel.grid.minor.y = element_line(colour = gray(0.6),linetype = 2),
            panel.ontop = TRUE)
  }
  
  p1 = p1 + coord_cartesian(xlim=xlimits, ylim=ylimits,expand = FALSE)
  
  if (!missing(wrap)) {
    p1 = p1 + facet_wrap(as.formula(wrap))
  }
  
  return(p1)
}
  

Now let’s plot genus-level accuracy for a model taking quality labels into account:

results = read_and_process_xval('Malpighiaceae+Chrysobalanaceae/varKoder/vit_results/')
summary_genus = summarize_results(results,'genus')
p_genus = plot_area(summary_genus, 'varKoder genus', relative = TRUE)
p_genus

Now the same but with species

summary_species = summarize_results(results,'species')
p_species = plot_area(summary_species, 'varKoder species', relative = TRUE)
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_species

Finally, family

summary_family = summarize_results(results,'family')
p_family = plot_area(summary_family, 'varKoder family', relative = TRUE)
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_family

what explains the errors?

Now we will try to identify which samples failed and why they failed. Particuarly, how do DNA quality, amount of data, and the number of samples per class impact results? We will use genus-level predictions to test.

genus_predictions = results %>%
  mutate(predicted_genus = str_extract(predicted_labels, 'genus:[^;]*'),
         actual_genus = str_extract(actual_labels, 'genus:[^;]*')) %>%
  select(-starts_with('family'),-starts_with('species')) %>%
  pivot_longer(cols = starts_with("genus"), names_to = "predicted_label", values_to = "confidence") %>%
  filter(actual_genus == predicted_label) %>%
  select(query_bp, sample_id, basefrequency_sd, actual_genus, confidence) %>%
  mutate(query_bp = 1000*(str_remove(query_bp, "K") %>% as.integer))

genus_predictions = genus_predictions %>%
  select(sample_id, actual_genus) %>%
  distinct() %>%
  group_by(actual_genus) %>%
  summarise(N_samples = n()) %>%
  right_join(genus_predictions)
Joining with `by = join_by(actual_genus)`
genus_predictions

Now let’s make some plots. First, what is the effect of number of samples per class in confidence?

set.seed(13214526)
plot_genus_N_vs_conf = ggplot(genus_predictions, aes(x = N_samples-1, 
                              y = confidence)) + 
  scale_color_viridis_c() +
  geom_jitter(alpha=0.3) + 
  scale_x_log10() +
  #ylab('Confidence in correct prediction\n(logit scale)') +
  ylab('Confidence in correct prediction') +
  xlab('Number of samples in correct genus\n(log scale)') +
  #scale_y_continuous(trans = "logit", breaks = c(1e-4,0.001,0.01,0.1,0.25,0.5,0.75,0.9,0.99,0.999,1-1e-4)) +
  scale_y_continuous(limits=c(0,1)) +
  theme_few() +
  theme(panel.grid.major.y = element_line(colour = gray(0.8)))

plot_genus_N_vs_conf

Now, what is the effect of sample quality in confidence?

set.seed(13214526)
plot_genus_freqsd_vs_conf = ggplot(genus_predictions, aes(x = basefrequency_sd, y = confidence)) + 
  geom_point(alpha=0.3) + 
  scale_x_log10() +
  #scale_y_continuous(trans = "logit", breaks = c(1e-4,0.001,0.01,0.1,0.25,0.5,0.75,0.9,0.99,0.999,1-1e-4)) +
  scale_y_continuous(limits=c(0,1)) +
  #ylab('Confidence in correct prediction\n(logit scale)') +
  ylab('Confidence in correct prediction') +
  xlab('Standard deviation of base frequencies') +
  theme_few() +
  theme(panel.grid.major.y = element_line(colour = gray(0.8)))

plot_genus_freqsd_vs_conf

Now, what is the effect of amount of data in confidence?

set.seed(13214526)
plot_genus_bp_vs_conf = ggplot(genus_predictions, aes(x = query_bp, y = confidence)) + 
  geom_jitter(alpha=0.3) + 
  #scale_y_continuous(trans = "logit", breaks = c(1e-4,0.001,0.01,0.1,0.25,0.5,0.75,0.9,0.99,0.999,1-1e-4)) +
  scale_y_continuous(limits=c(0,1)) +
  #ylab('Confidence in correct prediction\n(logit scale)') +
  ylab('Confidence in correct prediction') +
  xlab('Base pairs in query images\n(log scale)') +
  scale_x_log10() +
  theme_few() +
  theme(panel.grid.major.y = element_line(colour = gray(0.8)))

plot_genus_bp_vs_conf

Now let’s save the three of them as a single plot using cowplot.

combined_conf = patchwork::wrap_plots(plot_genus_N_vs_conf + theme(text = element_text(size=8)),
                                      plot_genus_bp_vs_conf + theme(axis.title.y=element_blank(), 
                                                                    axis.text.y=element_blank(), 
                                                                    text = element_text(size=8)),
                                      plot_genus_freqsd_vs_conf + theme(axis.title.y=element_blank(), 
                                                                        axis.text.y=element_blank(),
                                                                        text = element_text(size=8))) +
  patchwork::plot_annotation(tag_levels = 'A') 

combined_conf

ggsave(filename = 'images_manuscript/supp_conf_predictors.pdf',device = 'pdf',width = 7,height=3,units = 'in',useDingbats=F)

Let’s put it all together now in a linear model:

lm_data = genus_predictions %>%
  mutate(confidence = ifelse(confidence == 1, confidence-0.0000001, confidence),
         confidence = car::logit(confidence)) %>%
  mutate(query_bp = (query_bp - mean(query_bp))/sd(query_bp),
         basefrequency_sd = (basefrequency_sd - mean(basefrequency_sd))/sd(basefrequency_sd),
         N_samples = (N_samples - mean(N_samples))/sd(N_samples)
         ) 

full_model = lm(formula = confidence~query_bp*basefrequency_sd*N_samples, data = lm_data) 
full_model

Call:
lm(formula = confidence ~ query_bp * basefrequency_sd * N_samples, 
    data = lm_data)

Coefficients:
                        (Intercept)                             query_bp                     basefrequency_sd  
                            4.98965                              0.16528                             -0.56243  
                          N_samples            query_bp:basefrequency_sd                   query_bp:N_samples  
                            1.63728                              0.21806                              0.03515  
         basefrequency_sd:N_samples  query_bp:basefrequency_sd:N_samples  
                            0.05848                              0.09003  
summary(full_model)

Call:
lm(formula = confidence ~ query_bp * basefrequency_sd * N_samples, 
    data = lm_data)

Residuals:
     Min       1Q   Median       3Q      Max 
-13.9183  -0.9908   0.3121   1.3745   6.7384 

Coefficients:
                                    Estimate Std. Error t value Pr(>|t|)    
(Intercept)                          4.98965    0.05103  97.780  < 2e-16 ***
query_bp                             0.16528    0.06837   2.418   0.0157 *  
basefrequency_sd                    -0.56243    0.09834  -5.719 1.21e-08 ***
N_samples                            1.63728    0.05381  30.426  < 2e-16 ***
query_bp:basefrequency_sd            0.21806    0.17915   1.217   0.2236    
query_bp:N_samples                   0.03515    0.07366   0.477   0.6333    
basefrequency_sd:N_samples           0.05848    0.11746   0.498   0.6186    
query_bp:basefrequency_sd:N_samples  0.09003    0.21217   0.424   0.6714    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 2.101 on 2251 degrees of freedom
Multiple R-squared:  0.443, Adjusted R-squared:  0.4413 
F-statistic: 255.8 on 7 and 2251 DF,  p-value: < 2.2e-16
plot(full_model)

reduced_model = step(full_model, direction ="both")
Start:  AIC=3363.26
confidence ~ query_bp * basefrequency_sd * N_samples

                                      Df Sum of Sq    RSS    AIC
- query_bp:basefrequency_sd:N_samples  1   0.79515 9941.8 3361.4
<none>                                             9941.0 3363.3

Step:  AIC=3361.44
confidence ~ query_bp + basefrequency_sd + N_samples + query_bp:basefrequency_sd + 
    query_bp:N_samples + basefrequency_sd:N_samples

                                      Df Sum of Sq    RSS    AIC
- query_bp:N_samples                   1    0.2369 9942.0 3359.5
- basefrequency_sd:N_samples           1    0.3082 9942.1 3359.5
- query_bp:basefrequency_sd            1    7.6355 9949.4 3361.2
<none>                                             9941.8 3361.4
+ query_bp:basefrequency_sd:N_samples  1    0.7952 9941.0 3363.3

Step:  AIC=3359.49
confidence ~ query_bp + basefrequency_sd + N_samples + query_bp:basefrequency_sd + 
    basefrequency_sd:N_samples

                             Df Sum of Sq    RSS    AIC
- basefrequency_sd:N_samples  1    0.2658 9942.3 3357.6
- query_bp:basefrequency_sd   1    7.4113 9949.4 3359.2
<none>                                    9942.0 3359.5
+ query_bp:N_samples          1    0.2369 9941.8 3361.4

Step:  AIC=3357.56
confidence ~ query_bp + basefrequency_sd + N_samples + query_bp:basefrequency_sd

                             Df Sum of Sq     RSS    AIC
- query_bp:basefrequency_sd   1       7.2  9949.5 3357.2
<none>                                     9942.3 3357.6
+ basefrequency_sd:N_samples  1       0.3  9942.0 3359.5
+ query_bp:N_samples          1       0.2  9942.1 3359.5
- N_samples                   1    5618.2 15560.5 4367.5

Step:  AIC=3357.2
confidence ~ query_bp + basefrequency_sd + N_samples

                             Df Sum of Sq     RSS    AIC
<none>                                     9949.5 3357.2
+ query_bp:basefrequency_sd   1       7.2  9942.3 3357.6
+ basefrequency_sd:N_samples  1       0.1  9949.4 3359.2
+ query_bp:N_samples          1       0.0  9949.5 3359.2
- query_bp                    1      27.9  9977.4 3361.5
- basefrequency_sd            1     942.4 10891.9 3559.6
- N_samples                   1    5611.2 15560.7 4365.5
reduced_model

Call:
lm(formula = confidence ~ query_bp + basefrequency_sd + N_samples, 
    data = lm_data)

Coefficients:
     (Intercept)          query_bp  basefrequency_sd         N_samples  
          4.9648            0.1133           -0.6564            1.6207  
summary(reduced_model)

Call:
lm(formula = confidence ~ query_bp + basefrequency_sd + N_samples, 
    data = lm_data)

Residuals:
     Min       1Q   Median       3Q      Max 
-13.9246  -0.9992   0.3124   1.3751   6.7283 

Coefficients:
                 Estimate Std. Error t value Pr(>|t|)    
(Intercept)       4.96484    0.04419 112.340   <2e-16 ***
query_bp          0.11326    0.04506   2.513    0.012 *  
basefrequency_sd -0.65645    0.04492 -14.615   <2e-16 ***
N_samples         1.62074    0.04545  35.661   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 2.101 on 2255 degrees of freedom
Multiple R-squared:  0.4425,    Adjusted R-squared:  0.4418 
F-statistic: 596.7 on 3 and 2255 DF,  p-value: < 2.2e-16
plot(reduced_model)

Skmer

For skmer, we left each sample out, built a reference and then queried that sample. We have several files in which reference samples are ordered by their distance to the query, we here we will evaluate whether the closest sample is from the correct species or genus.

Because it is not clear how skmer behaves for different levels of coverage, we repeated this for several input sizes (in number of basepairs) as query, but always used the maximum input dize available (up to 200Mb) for references.

Let’s make a function that extracts these results as a table.


samp_labels = results %>% select(sample_id,actual_labels) %>% distinct()

extract_skmer_results = function(file_path) {
    # Read only the first 2 lines of the file
    file_lines <- readLines(file_path, n = 2)
    
    # Extract sample_ID, basepairs from the first line
    sample_info <- str_match(file_lines[1], "\\s*(.*?)@(\\d+K)")[, 2:3]
    sample_ID <- sample_info[1]
    basepairs <- sample_info[2]
    
    # Extract reference_sample_ID, distance from the second line
    reference_info <- str_match(file_lines[2], "\\s*(.*?)@.*\\s+(\\d+\\.\\d+)")[, 2:3]
    reference_sample_ID <- reference_info[1]
    distance <- as.numeric(reference_info[2])
    
    # Create a tibble
    tibble(
        sample_id = sample_ID,
        query_bp = basepairs,
        closest_reference_sample_id = reference_sample_ID,
        closest_distance = distance
    ) 
}

Now we will apply this function to all skmer output files.

plan(multisession(workers = 12))
skmer_results_df = furrr::future_map_dfr(
  list.files('Malpighiaceae+Chrysobalanaceae/skmer/skmer_xval_results/', full.names = T),
  ~ extract_skmer_results(.x)
) %>%
  left_join(samp_labels, by = 'sample_id') %>%
  left_join(
    samp_labels %>% select(
      closest_reference_sample_id = 'sample_id',
      predicted_labels = actual_labels
    ),
    by = 'closest_reference_sample_id'
  ) %>%
  mutate(
    query_labels = str_remove(actual_labels, ";*low_quality:True;*") %>% str_split(';'),
    predicted_list = str_split(predicted_labels, ';')
  ) %>%
  rowwise() %>%
  mutate(
    family_correct = query_labels[str_detect(query_labels, 'family')] %in% predicted_list,
    genus_correct = query_labels[str_detect(query_labels, 'genus')] %in% predicted_list,
    species_correct = ifelse(any(str_detect(
      query_labels, 'species'
    )),
    query_labels[str_detect(query_labels, 'species')] %in% predicted_list,
    NA),
    family_incorrect = any(!(predicted_list[str_detect(predicted_list, 'family')] %in% query_labels[str_detect(query_labels, 'family')])),
    genus_incorrect = any(!(predicted_list[str_detect(predicted_list, 'genus')] %in% query_labels[str_detect(query_labels, 'genus')])),
    species_incorrect = ifelse(any(str_detect(
      query_labels, 'species'
    )),
    any(!(
      predicted_list[str_detect(predicted_list, 'species')] %in% query_labels[str_detect(query_labels, 'species')]
    )),
    NA)
    
  )
plan(sequential)
skmer_results_df

Now let’s summarize and plot by genus:

skmer_summary_genus = summarize_results(skmer_results_df,'genus')
p_skmer_genus = plot_area(skmer_summary_genus, 'Skmer genus', relative = TRUE)
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_skmer_genus

Now by species. In Skmer, there is no inconclusive result: if there is no correct species prediction, it means that a sample was predicted in the wrong genus and therefore it is incorrect

skmer_summary_species = summarize_results(skmer_results_df,'species') %>%
  mutate(result = ifelse(result == 'correct', 'correct','incorrect')) %>%
  group_by(query_bp,result) %>%
  summarise_all(sum)
p_skmer_species = plot_area(skmer_summary_species, 'Skmer species', relative = TRUE)
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_skmer_species

And now by family:

skmer_summary_family = summarize_results(skmer_results_df,'family')
skmer_summary_family 
p_skmer_family = plot_area(skmer_summary_family, 'Skmer family', relative = TRUE)
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_skmer_family

Traditional barcodes

BLAST single gene

Let’s now read the traditional barcode BLAST results and summarize them in the same way as skmer and varKoder. Let’s start by defining a fuction that reads the data so we can summarize it using the previously defined functions.

read_traditional_barcodes = function(bp) {
  input_file = paste0(
    'Malpighiaceae+Chrysobalanaceae/traditional_barcodes/2_blast_phylogeny_result/Genus/',
    bp,
    'M_blast_phylo_sum_sp.tsv'
  )
  
  barcode_res = read_delim(input_file) %>%
    pivot_longer(-sp, names_to = 'marker', values_to = 'closest_reference_sample_id') %>%
    rename(sample_id = 'sp') %>%
    mutate(
      sample_id = str_remove_all(sample_id, '@.+'),
      closest_reference_sample_id = str_remove_all(closest_reference_sample_id, '@.+'),
      predicted_labels = samp_labels$actual_labels[match(closest_reference_sample_id, samp_labels$sample_id)],
      actual_labels = samp_labels$actual_labels[match(sample_id, samp_labels$sample_id)]
    ) %>%
    filter(marker != 'Concatenated_phylogeny') %>%
    mutate(
      query_labels = str_remove(actual_labels, ";*low_quality:True;*") %>% str_split(';'),
      predicted_list = str_split(predicted_labels, ';')
    ) %>%
    rowwise() %>%
    mutate(
      family_correct = query_labels[str_detect(query_labels, 'family')] %in% predicted_list,
      genus_correct = query_labels[str_detect(query_labels, 'genus')] %in% predicted_list,
      species_correct = ifelse(any(str_detect(
        query_labels, 'species'
      )),
      query_labels[str_detect(query_labels, 'species')] %in% predicted_list,
      NA),
      family_incorrect = any(!(predicted_list[str_detect(predicted_list, 'family')] %in% query_labels[str_detect(query_labels, 'family')])),
      genus_incorrect = any(!(predicted_list[str_detect(predicted_list, 'genus')] %in% query_labels[str_detect(query_labels, 'genus')])),
      species_incorrect = ifelse(any(str_detect(
        query_labels, 'species'
      )),
      any(!(
        predicted_list[str_detect(predicted_list, 'species')] %in% query_labels[str_detect(query_labels, 'species')]
      )),
      NA)
    ) %>%
    mutate_at(vars(ends_with("_correct"), ends_with("_incorrect")),
              ~ ifelse(is.na(predicted_labels) & !is.na(.), FALSE, .)) %>%
    mutate(query_bp = bp * 1e3)
  
  return(barcode_res)
}

Now we can apply this function to all of our results:

results_barcodes = purrr::map_dfr(c(0.5,1,2,5,10,20,50,100,200),read_traditional_barcodes)
Warning: One or more parsing issues, call `problems()` on your data frame for details, e.g.:
  dat <- vroom(...)
  problems(dat)Rows: 288 Columns: 7── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (6): sp, matK, rbcL, ndhF, trnL-F, ITS
lgl (1): Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Warning: One or more parsing issues, call `problems()` on your data frame for details, e.g.:
  dat <- vroom(...)
  problems(dat)Rows: 288 Columns: 7── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (6): sp, matK, rbcL, ndhF, trnL-F, ITS
lgl (1): Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Warning: One or more parsing issues, call `problems()` on your data frame for details, e.g.:
  dat <- vroom(...)
  problems(dat)Rows: 288 Columns: 7── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (6): sp, matK, rbcL, ndhF, trnL-F, ITS
lgl (1): Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Warning: One or more parsing issues, call `problems()` on your data frame for details, e.g.:
  dat <- vroom(...)
  problems(dat)Rows: 288 Columns: 7── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (6): sp, matK, rbcL, ndhF, trnL-F, ITS
lgl (1): Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Rows: 288 Columns: 7── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (7): sp, matK, rbcL, ndhF, trnL-F, ITS, Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Rows: 285 Columns: 7── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (7): sp, matK, rbcL, ndhF, trnL-F, ITS, Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Rows: 267 Columns: 7── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (7): sp, matK, rbcL, ndhF, trnL-F, ITS, Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Rows: 200 Columns: 7── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (7): sp, matK, rbcL, ndhF, trnL-F, ITS, Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Rows: 166 Columns: 7── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (7): sp, matK, rbcL, ndhF, trnL-F, ITS, Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
results_barcodes

Now let’s summarise for each marker separately:

barcode_summary_family = split(results_barcodes,results_barcodes$marker) %>%
  purrr::map_dfr(~summarize_results(.x,'family'),.id='marker')

barcode_summary_family
barcode_summary_genus = split(results_barcodes,results_barcodes$marker) %>%
  purrr::map_dfr(~summarize_results(.x,'genus'),.id='marker')

barcode_summary_genus
barcode_summary_species = split(results_barcodes,results_barcodes$marker) %>%
  purrr::map_dfr(~summarize_results(.x,'species'),.id='marker')

barcode_summary_species

Now let’s plot, making separate plots for each marker:

Species:

p_barcode_species = barcode_summary_species %>%
  split(barcode_summary_species$marker) %>%
  purrr::map(~plot_area(.x,paste0(unique(.x$marker),' species'), relative = TRUE, xlim_all = TRUE))
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_barcode_species
$ITS

$matK

$ndhF

$rbcL

$`trnL-F`

Genera:

p_barcode_genus = barcode_summary_genus %>%
  split(barcode_summary_genus$marker) %>%
  purrr::map(~plot_area(.x,paste0(unique(.x$marker),' genus'), relative = TRUE, xlim_all = TRUE))
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_barcode_genus
$ITS

$matK

$ndhF

$rbcL

$`trnL-F`

Family:

p_barcode_family = barcode_summary_family %>%
  split(barcode_summary_family$marker) %>%
  purrr::map(~plot_area(.x,paste0(unique(.x$marker),' family'), relative = TRUE,xlim_all = TRUE))
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_barcode_family
$ITS

$matK

$ndhF

$rbcL

$`trnL-F`

Concatenated tree

Now we will do the same for concatenated tree. Let’s start by defining a function to gather results. We will consider a result as correct if the majority of the sister taxon to a tip has the same label.


read_concatenated_tree_results = function(bp){
  
  
# Read in your tree - replace 'your_tree_file.nwk' with the path to your tree file
tree = read.tree(paste0('Malpighiaceae+Chrysobalanaceae/traditional_barcodes/2_blast_phylogeny_result/Genus/conc.',bp,'m.spname.tre'))

#leave only sample IDs as tip labels
tree$tip.label = tree$tip.label %>% str_remove(".*@") %>% str_remove("'") %>% str_replace(' ref','_ref')

# Compute the patristic distances and list all reference names
patristic_distances <- cophenetic(tree)
all_ref_names = dimnames(patristic_distances)[[1]][str_detect(dimnames(patristic_distances)[[1]],'_ref$')]
all_nonref = dimnames(patristic_distances)[[1]][str_detect(dimnames(patristic_distances)[[1]],'_ref$',negate = TRUE)]

# For each tip, find the reference sample with closest patristic distance
find_closest = function(tip){
  to_keep = c(tip,all_ref_names[str_detect(all_ref_names,paste0(tip,'_ref'),negate = TRUE)])
  return(names(sort(patristic_distances[tip,to_keep])[2]) %>%
           str_remove('_ref'))
}

closest_match = purrr::map_chr(all_nonref,find_closest)

samples_with_data = read_delim(paste0('Malpighiaceae+Chrysobalanaceae/traditional_barcodes/2_blast_phylogeny_result/Genus/',bp,'M_blast_phylo_sum_sp.tsv')) %>% 
  select(sample_id=sp) %>%
  mutate(sample_id = str_remove_all(sample_id, '@.+'))

barcode_res = tibble(sample_id = all_nonref,
       closest_reference_sample_id = closest_match) %>%
  right_join(samples_with_data) %>%
  mutate(
      predicted_labels = samp_labels$actual_labels[match(closest_reference_sample_id, samp_labels$sample_id)],
      actual_labels = samp_labels$actual_labels[match(sample_id, samp_labels$sample_id)]
    ) %>%
  filter(sample_id!='2095') %>%
  mutate(
      query_labels = str_remove(actual_labels, ";*low_quality:True;*") %>% str_split(';'),
      predicted_list = str_split(predicted_labels, ';')
    ) %>%
    rowwise() %>%
    mutate(
      family_correct = query_labels[str_detect(query_labels, 'family')] %in% predicted_list,
      genus_correct = query_labels[str_detect(query_labels, 'genus')] %in% predicted_list,
      species_correct = ifelse(any(str_detect(
        query_labels, 'species'
      )),
      query_labels[str_detect(query_labels, 'species')] %in% predicted_list,
      NA),
      family_incorrect = any(!(predicted_list[str_detect(predicted_list, 'family')] %in% query_labels[str_detect(query_labels, 'family')])),
      genus_incorrect = any(!(predicted_list[str_detect(predicted_list, 'genus')] %in% query_labels[str_detect(query_labels, 'genus')])),
      species_incorrect = ifelse(any(str_detect(
        query_labels, 'species'
      )),
      any(!(
        predicted_list[str_detect(predicted_list, 'species')] %in% query_labels[str_detect(query_labels, 'species')]
      )),
      NA)
    ) %>%
    mutate_at(vars(ends_with("_correct"), ends_with("_incorrect")),
              ~ ifelse(is.na(predicted_labels) & !is.na(.), FALSE, .)) %>%
    mutate(query_bp = bp * 1e3)
  
  return(barcode_res)
}

Now let’s apply this function

results_concat_barcodes = purrr::map_dfr(c(0.5,1,2,5,10,20,50,100,200),read_concatenated_tree_results)
Warning: One or more parsing issues, call `problems()` on your data frame for details, e.g.:
  dat <- vroom(...)
  problems(dat)Rows: 288 Columns: 7── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (6): sp, matK, rbcL, ndhF, trnL-F, ITS
lgl (1): Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(sample_id)`Warning: One or more parsing issues, call `problems()` on your data frame for details, e.g.:
  dat <- vroom(...)
  problems(dat)Rows: 288 Columns: 7── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (6): sp, matK, rbcL, ndhF, trnL-F, ITS
lgl (1): Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(sample_id)`Warning: One or more parsing issues, call `problems()` on your data frame for details, e.g.:
  dat <- vroom(...)
  problems(dat)Rows: 288 Columns: 7── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (6): sp, matK, rbcL, ndhF, trnL-F, ITS
lgl (1): Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(sample_id)`Warning: One or more parsing issues, call `problems()` on your data frame for details, e.g.:
  dat <- vroom(...)
  problems(dat)Rows: 288 Columns: 7── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (6): sp, matK, rbcL, ndhF, trnL-F, ITS
lgl (1): Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(sample_id)`Rows: 288 Columns: 7── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (7): sp, matK, rbcL, ndhF, trnL-F, ITS, Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(sample_id)`Rows: 285 Columns: 7── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (7): sp, matK, rbcL, ndhF, trnL-F, ITS, Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(sample_id)`Rows: 267 Columns: 7── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (7): sp, matK, rbcL, ndhF, trnL-F, ITS, Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(sample_id)`Rows: 200 Columns: 7── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (7): sp, matK, rbcL, ndhF, trnL-F, ITS, Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(sample_id)`Rows: 166 Columns: 7── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (7): sp, matK, rbcL, ndhF, trnL-F, ITS, Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(sample_id)`
results_concat_barcodes

Let’s summarize results and plot for genus, species and family accuracy

concat_summary_species = summarize_results(results_concat_barcodes,'species')
p_concat_species = plot_area(concat_summary_species, relative = FALSE,title = 'Concatenated barcodes species',xlim_all = TRUE)
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_concat_species

concat_summary_genus = summarize_results(results_concat_barcodes,'genus')
p_concat_genus = plot_area(concat_summary_genus, relative = TRUE,title = 'Concatenated barcodes genus',xlim_all = TRUE)
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_concat_genus

concat_summary_family = summarize_results(results_concat_barcodes,'family')
p_concat_family = plot_area(concat_summary_family, relative = TRUE,title = 'Concatenated barcodes family',xlim_all = TRUE)
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_concat_family

Direct comparison

Now let’s compare methods side by side. For genus level:

p = patchwork::wrap_plots(p_genus + theme(axis.text.x = element_blank(),
                                           axis.title.x = element_blank()), 
                   p_skmer_genus + theme(axis.text.x = element_blank(),
                                           axis.title.x = element_blank()),
                   p_barcode_genus$ITS + theme(axis.text.x = element_blank(),
                                           axis.title.x = element_blank()),
                   p_barcode_genus$rbcL + theme(axis.text.x = element_blank(),
                                           axis.title.x = element_blank()),
                   p_concat_genus,
                   ncol = 1) +
  plot_annotation(title = 'Genus-level accuracy')
p

ggsave('images_manuscript/fig3_genus_accuracy.pdf', width=5,height = 10)
ggsave('images_manuscript/fig3_genus_accuracy.png', width=5,height = 10,dpi=1200)

Now for species level:

p = patchwork::wrap_plots(p_species + theme(axis.text.x = element_blank(),
                                           axis.title.x = element_blank()), 
                   p_skmer_species + theme(axis.text.x = element_blank(),
                                           axis.title.x = element_blank()),
                   p_barcode_species$ITS + theme(axis.text.x = element_blank(),
                                           axis.title.x = element_blank()),
                   p_barcode_species$rbcL + theme(axis.text.x = element_blank(),
                                           axis.title.x = element_blank()),
                   p_concat_species,
                   ncol = 1) +
  plot_annotation(title = 'species-level accuracy')
p

ggsave('images_manuscript/fig3_species_accuracy.pdf', width=5,height = 10)
ggsave('images_manuscript/fig3_species_accuracy.png', width=5,height = 10,dpi=1200)

Now for family level:

p = patchwork::wrap_plots(p_family + theme(axis.text.x = element_blank(),
                                           axis.title.x = element_blank()), 
                   p_skmer_family + theme(axis.text.x = element_blank(),
                                           axis.title.x = element_blank()),
                   p_barcode_family$ITS + theme(axis.text.x = element_blank(),
                                           axis.title.x = element_blank()),
                   p_barcode_family$rbcL + theme(axis.text.x = element_blank(),
                                           axis.title.x = element_blank()),
                   p_concat_family,
                   ncol = 1) +
  plot_annotation(title = 'family-level accuracy')
p

ggsave('images_manuscript/fig3_family_accuracy.pdf', width=5,height = 10)
ggsave('images_manuscript/fig3_family_accuracy.png', width=5,height = 10,dpi=1200)

Comparison of run times

Now let’s compare the time to produce references and to produce

SRA

Finally, let’s summarize results for the whole SRA dataset. In this case, we only have varKoder since Skmer cannot finish and traditional barcodes are inapplicable.

varKoder_SRA_results  = read_csv('all_SRA/varkoder_query_results/predictions.csv') %>%
select(-1) %>%
  filter(str_detect(query_basepairs,'^0+[125]0+K$')) %>% #we will ignore queries that are not standardized sizes
  rename(query_bp = query_basepairs) %>%
  mutate(quality_included = T)
plan(sequential)

SRA_taxlabels = str_remove(varKoder_SRA_results$actual_labels,";*low_quality:True;*") %>% str_split(';') %>% unlist %>% unique

varKoder_SRA_results = varKoder_SRA_results %>%
  mutate(query_labels = str_remove(actual_labels,";*low_quality:True;*") %>% str_split(';') %>% unlist,
         predicted_list = str_split(predicted_labels,';')
         ) %>%
  rowwise() %>%
  mutate(family_correct = query_labels %in% predicted_list,
         family_incorrect = ifelse(is.na(predicted_labels),FALSE,any(!(predicted_list %in% query_labels)))) %>%
 select(matches("^[^0-9]"))

varKoder_SRA_results 
         

Now let’s summarize and plot:

SRA_summary_family = summarize_results(varKoder_SRA_results,'family')
SRA_summary_family

N_samp = SRA_summary_family %>%
 group_by(query_bp) %>%
 summarise(N = sum(N))

p_SRA_family = plot_area(SRA_summary_family, 'varKoder SRA family', relative = TRUE,xlim_all = FALSE) 
p_SRA_family 

Let’s now do the SRA plot, but splitting by kingdom. First, we need to retrieve kingdom information:


p_SRA_families = read_csv('all_SRA/runs_to_download_data.csv') %>%
  select(sample_id = Run, Kingdom) %>%
  right_join(varKoder_SRA_results) %>%
  split(.$Kingdom) %>%
  purrr::map_df(summarize_results, 
                 level='family',
                .id='Kingdom'
                ) %>%
  mutate(Kingdom = factor(Kingdom,levels=c('Metazoa','Viridiplantae','Fungi'),ordered = T)) %>%
  plot_area(relative=FALSE,xlim_all = FALSE,wrap = '~Kingdom',title='Families in SRA') + coord_cartesian(xlim=c(500,10000)*1000,expand = FALSE)

print(p_SRA_families)


ggsave('images_manuscript/fig3_SRA_accuracy.pdf', width=4.5,height = 4)
ggsave('images_manuscript/fig3_SRA_accuracy.png', width=4.5,height = 4,dpi = 1200)

Other datasets

Now we will make a small figure to include the additional datasets in which we applied varKoding.

In these cases, we chose a test set that included both taxa in the training set and taxa not in the training set, so we will graph both separately. This is denoted by a column named in_training_model. Let’s start by reading results.

Let’s define a function to read and process predictions:

read_and_process_others = function(infile){
  
varkoder_results = read_csv(infile) %>% 
  mutate(sample_id = as.character(sample_id)) %>%
  select(-1) %>%
  rename(query_bp = query_basepairs) 


all_taxlabels = str_remove(varkoder_results$actual_labels,";*low_quality:True;*") %>% str_split(';') %>% unlist %>% unique

varkoder_results = varkoder_results %>%
  mutate(query_labels = str_remove(actual_labels,";*low_quality:True;*") %>% str_split(';'),
         predicted_list = str_split(predicted_labels,';')
         ) %>%
  rowwise() %>%
  mutate(taxon_correct = any(query_labels %in% predicted_list),
         taxon_incorrect = any(!(predicted_list[!is.na(predicted_list)] %in% query_labels))
         )

return(varkoder_results)
}

Now let’s apply this function to all files.

prediction_files = list.files('other_datasets',pattern = 'prediction.+csv',full.names = T)
names(prediction_files) = basename(prediction_files) %>% str_extract(".*(?=_prediction_table\\.csv)")

other_results = purrr::map_dfr(prediction_files, read_and_process_others, .id='dataset')
other_results

Let’s now summarize by dataset and separately for taxa included and excluded from the training set.

summary_others = other_results %>%
  split(interaction(other_results$dataset, other_results$in_training_model)) %>%
  purrr::map_dfr(summarize_results, level = 'taxon', .id = 'comb') %>%
  separate(comb, into = c("dataset", "taxon_in_training_raw"), sep = "\\.") %>%
  mutate(taxon_in_training = taxon_in_training_raw == 'yes') %>%
  select(-taxon_in_training_raw) %>%
  mutate(taxon_in_training = c('Taxon not in training set', 'Taxon in training set')[taxon_in_training+1],
         dataset = str_replace(dataset, "^(.)", ~toupper(.x))) %>%
  mutate(result = factor(result,
                         levels=c("correct", "ambiguous", "inconclusive", "incorrect"),
                         ordered=T))

summary_others

Now let’s plot

p_others = ggplot(summary_others , aes(x = dataset, y = N, fill = result)) +
  geom_col()+
    scale_fill_manual(values = setNames(RColorBrewer::brewer.pal(4, "Accent"), c("correct", "ambiguous", "inconclusive", "incorrect"))) +
    scale_alpha_manual(values=c(0.5,1)) +
    ggtitle('Other datasets') +
    ylab('Number of samples') +
    theme_few() +
      scale_y_continuous(minor_breaks = waiver()) +
      theme(panel.background = element_rect(fill = NA),
            panel.grid.major.y = element_line(colour = gray(0.5)),
            panel.grid.minor.y = element_line(colour = gray(0.6),linetype = 2),
            axis.title.x = element_blank(),
            axis.text.x = element_text(face='italic'),
            panel.ontop = TRUE) +
    coord_cartesian(expand=FALSE) +
    facet_grid(taxon_in_training~.)
  
p_others

ggsave('images_manuscript/fig3_others_accuracy.pdf', width=4.5,height = 4)
ggsave('images_manuscript/fig3_others_accuracy.png', width=4.5,height = 4,dpi = 1200)

Generating numbers for publication

Here we just query our results to get a few figures that we report in the paper.

Total number of samples used in cross-validation:

dim(samp_labels)

Number of Stigmaphyllon samples with each kind of error for varkoder:

summary_species

Number of Stigmaphyllon samples with each kind of error for skmer:

skmer_summary_species

Traditional barcode accuracy for species:

barcode_summary_species %>% arrange(query_bp,marker)

Concatenated barcode accuract for species:

concat_summary_species

varKoder accuracy for genera:

summary_genus

varKoder accuracy for family:

summary_family

Skmer accuracy for genera:

skmer_summary_genus

Skmer accuracy for family:

skmer_summary_family

Number of samples available for each genus and data amount

results %>%
  mutate(genus = str_extract(actual_labels,"(?<=genus:)[^;]+")) %>%
  group_by(query_bp) %>%
  summarize(N=n()) %>%
  complete()

Plot number of samples for supplementary material.

n_samples_genera = results %>%
  mutate(taxon = str_extract(actual_labels,"(?<=genus:)[^;]+")) %>%
  group_by(taxon, query_bp) %>%
  summarize(N=n()) %>%
  ungroup() %>%
  complete(taxon, query_bp, fill = list(N=0)) %>%
  mutate(taxon = fct_reorder(taxon, N))
n_samples_genera 

n_samples_species = results %>%
  mutate(taxon = str_extract(actual_labels,"(?<=species:)[^;]+")) %>%
  filter(!is.na(taxon)) %>%
  group_by(taxon, query_bp) %>%
  summarize(N=n()) %>%
  ungroup() %>%
  complete(taxon, query_bp, fill = list(N=0))  %>%
  mutate(taxon = fct_reorder(taxon, N))
n_samples_species 

For SRA, we have to count both validation and training samples, since we did not do cross-validation. Let’s use image names to get the information and then the results table to figure out which ones were in the validation set.

all_files = c(list.files('all_SRA/varkoder_images_SRA/',pattern='*.png',recursive = T),
              list.files('all_SRA/varkoder_query_images/',pattern='*.png',recursive = T))

n_samples_SRA = data.frame(filename=all_files) %>%
  mutate(
    sample_id = str_extract(filename, "^(.+)(?=@)"),  # Capture everything up to the "@" symbol but exclude the symbol itself
    query_bp = str_extract(filename, "(?<=@)([0-9]+)K") # multiply by 1000 to convert K to the actual number
  ) %>% 
  left_join(read_csv('all_SRA/runs_to_download_data.csv') %>% 
              select(sample_id=Run,Kingdom,taxon=FamilyID)) %>%
  mutate_at(vars(taxon),as.character) %>%
  mutate(validation_set = sample_id %in% varKoder_SRA_results$sample_id) %>%
  group_by(taxon, query_bp,validation_set) %>%
  summarize(N=n()) %>%
  ungroup() %>%
  mutate(taxon = fct_reorder(taxon, N))

n_samples_SRA
  

  ((list.files('all_SRA/varkoder_images_SRA/',pattern='*.png',recursive = T) %>%
      str_extract("^(.+)(?=@)"))%in%
varKoder_SRA_results$sample_id) %>% summary
plot_Nsamples_area = function(df, title){
  df = df %>% 
    mutate(query_bp = parse_number(query_bp) *1000)
  
  n_levels <- length(unique(df$taxon))
  viridis_colors <- viridis::turbo(n_levels)
  
  half_n <- ceiling(n_levels / 2)
  reordered_colors <- c(rbind(viridis_colors[1:half_n], viridis_colors[(half_n + 1):n_levels]))


  
  
  ggplot(df, aes(x=query_bp,y=N,fill=taxon, color = taxon, group = taxon)) +
    geom_area(position= position_stack()) +
    #geom_line(position='stack') +
    scale_fill_manual(values = reordered_colors, 
                      aesthetics = c('colour','fill'),
                      guide = 'none') +
    scale_x_log10(labels = scales::label_number(scale_cut = scales::cut_si('bp')),
                  breaks = 1000*parse_number(unique(n_samples_genera$query_bp)),
                  limits = 1000*range(parse_number(unique(n_samples_genera$query_bp))))  +
    scale_y_continuous(n.breaks = 10, minor_breaks = waiver()) +
    ggtitle(title) +
    ylab('Number of samples') +
    xlab('Base pairs in query images') +
    theme_few() +
    theme(axis.text.x = element_text(hjust=1,angle=45),
          panel.background = element_rect(fill = NA),
            panel.grid.major.y = element_line(colour = gray(0.5)),
            panel.grid.minor.y = element_line(colour = gray(0.6),linetype = 2),
            panel.ontop = TRUE)
}
N_species = plot_Nsamples_area(n_samples_species,title='Stigmaphyllon Species')
N_genera = plot_Nsamples_area(n_samples_genera,title='Maplighiales Genera')
N_families = plot_Nsamples_area(n_samples_SRA,title='Eukaryotic familes') + facet_wrap(~validation_set)

p = cowplot::plot_grid(N_genera,N_species,N_families, nrow = 1)

print(p)

ggsave('images_manuscript/supp_fig_n_samples.pdf', width=8,height = 4)
ggsave('images_manuscript/supp_fig_n_samples.png', width=8,height = 4,dpi = 1200)

Total number of SRA samples. Validation:

read_csv('varKoder/all_SRA/varkoder_trained_model_ML/input_data.csv')[-1] %>%
  group_by(is_valid) %>%
  summarise(N = n())
LS0tCnRpdGxlOiAiVmFyS29kZXIsIFNrbWVyIGFuZCB0cmFkaXRpb25hbCBiYXJjb2RpbmcgQ3Jvc3MtdmFsaWRhdGlvbiIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVG8gY29tcGFyZSB0aGUgcGVyZm9ybWFuY2Ugb2YgdmFyS29kZSB0byBbU2ttZXJdKGh0dHBzOi8vZ2l0aHViLmNvbS9zaGFoYWItc2FybWFzaGdoaS9Ta21lciksIHdlIHdpbGwgdXNlIGxlYXZlLW9uZS1vdXQgY3Jvc3MgdmFsaWRhdGlvbjogd2UgcmVtb3ZlIG9uZSBzYW1wbGUgZnJvbSB0aGUgZGF0YXNldCwgdHJhaW4gYSB2YXJLb2RlIG1vZGVsIG9yIG1ha2UgYSBza21lciByZWZlcmVuY2Ugd2l0aCB0aGUgcmVtYWluaW5nIHNhbXBsZXMsIGFuZCB0aGVuIHVzZSB0aGUgc2FtcGxlIGxlZnQgb3V0IGFzIHF1ZXJ5LiBXZSB0aGVuIHJlY29yZCB3aGV0aGVyIG9yIG5vdCB3ZSBjb3JyZWN0bHkgaWRlbnRpZnkgdGhpcyBzYW1wbGUgaW4gdmFyS29kZXIsIGFuZCB3aGV0aGVyIG9yIG5vdCB0aGUgY2xvc2VzdCBzYW1wbGUgd2l0aCBTa21lciBoYXMgdGhlIHNhbWUgaWRlbnRpZmljYXRpb24uIAoKRm9yIHRyYWRpdGlvbmFsIGJhcmNvZGVzLCB3ZSBhc3NlbWJsZWQgdGhlIGdlbm9tZSBvZiBlYWNoIHNhbXBsZSwgYW5kIHRoZW4gdXNlZCBCTEFTVCB0byBzZWFyY2ggZm9yIGVhY2ggb2YgdGhlIHRyYWRpdGlvbmFsIGJhcmNvZGUgZ2VuZXMuIFdlIHJlY29yZGVkIGlmIHdlIGNvdWxkIGZpbmQgdGhpcyBnZW5lIGluIHRoZSBhc3NlbWJseSwgY29kaW5nIGFzIG1pc3NpbmcgZGF0YSBpZiB3ZSBjb3VsZCBub3QuIFdlIHRoZW4gcmVjb3JkZWQgd2hldGhlciB0aGUgYmVzdCBCTEFTVCBoaXQgZm9yIGEgc2FtcGxlIHdhcyB0aGUgY29ycmVjdCBzcGVjaWVzLgoKYGBge3J9CnJtKGxpc3Q9bHMoKSkKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZnV0dXJlKQpsaWJyYXJ5KGdndGhlbWVzKQpsaWJyYXJ5KHBhdGNod29yaykKbGlicmFyeShjb3dwbG90KQpsaWJyYXJ5KHBhdGNod29yaykKbGlicmFyeShwaHl0b29scykKbGlicmFyeShhcGUpCnNldC5zZWVkKDE0MTY0KQpgYGAKIyBWYXJLb2RlcgpGb3IgVmFyS29kZXIsIHdlIHVzZWQgbGVhdmUtb25lLW91dCBjcm9zcy12YWxpZGF0aW9uIHRvIHRlc3QgdGhlIGFjY3VyYWN5IGZvciBmYW1pbHksIGdlbmVyYSwgc3BlY2llcyBpbiB0aGUgam9pbnQgTWFscGlnaGlhY2VhZS1DaHJ5c29iYWxhbmFjZWFlIGRhdGFzZXQuIFdlIHVzZWQgYXMgaW5wdXQgZGF0YSB2YXJLb2RlcyBwcm9kdWNlZCBmcm9tIGttZXJzIG9mIHNpemUgNyBhbmQgNTAwS2JwIHRvIDIwME1icCBvZiBkYXRhLCBvciBhbGwgb2YgdGhlIGRhdGEgYXZhaWxhYmxlIGlmIGxlc3MgdGhhbiAyMDAgTWJwLiBGb3IgZWFjaCBzYW1wbGUsIHdlIGJ1aWx0IGEgbW9kZWwgdXNpbmcgYXMgaW5wdXQgZGF0YSBmcm9tIGFsbCBvdGhlciBzYW1wbGVzLiBUaGVuIHdlIHF1ZXJpZWQgdGhlIHNhbXBsZSBsZWZ0IG91dCwgdXNpbmcgYXMgaW5wdXQgdGhlIGltYWdlcyBnZW5lcmF0ZWQgZnJvbSA1MDBLYiB0byB0aGUgdG90YWwgZGF0YSBhdmFpbGFibGUuIE5vdyB3ZSB3aWxsIHN1bW1hcml6ZSB0aGUgcmVzdWx0cy4KCgojIyBBY2N1cmFjeSB2cyBkYXRhIGFtb3VudCBhbmQgdGF4b25vbWljIGxldmVscwoKSW4gdGhpcyB0ZXN0LCB3ZSB1c2VkIHZhcktvZGVyIFt2MC42LjBdKGh0dHBzOi8vZ2l0aHViLmNvbS9icnVub2FzbS92YXJLb2Rlci9yZWxlYXNlcy90YWcvdi4wLjYuMCkuIExldCdzIHByb2Nlc3MgdGhlIHJlc3VsdHMuCgpgYGB7cn0KcmVhZF9hbmRfcHJvY2Vzc194dmFsID0gZnVuY3Rpb24oaW5mb2xkZXIpewogIHBsYW4obXVsdGlzZXNzaW9uKHdvcmtlcnMgPSAxMikpCnZhcmtvZGVyX3Jlc3VsdHMgPSBsaXN0LmZpbGVzKGluZm9sZGVyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdwcmVkaWN0aW9ucy5jc3YnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlY3Vyc2l2ZT1ULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bGwubmFtZXMgPSBUKSAlPiUKICBmdXJycjo6ZnV0dXJlX21hcF9kZnIofnJlYWRfY3N2KC54KSAlPiUgbXV0YXRlKHNhbXBsZV9pZCA9IGFzLmNoYXJhY3RlcihzYW1wbGVfaWQpKSkgJT4lIAogIHNlbGVjdCgtMSkgJT4lCiAgZmlsdGVyKHN0cl9kZXRlY3QocXVlcnlfYmFzZXBhaXJzLCdeMCtbMTI1XTArSyQnKSkgJT4lICN3ZSB3aWxsIGlnbm9yZSBxdWVyaWVzIHRoYXQgYXJlIG5vdCBzdGFuZGFyZGl6ZWQgc2l6ZXMKICByZW5hbWUocXVlcnlfYnAgPSBxdWVyeV9iYXNlcGFpcnMpICU+JQogIG11dGF0ZShxdWFsaXR5X2luY2x1ZGVkID0gVCkKcGxhbihzZXF1ZW50aWFsKQoKYWxsX3RheGxhYmVscyA9IHN0cl9yZW1vdmUodmFya29kZXJfcmVzdWx0cyRhY3R1YWxfbGFiZWxzLCI7Kmxvd19xdWFsaXR5OlRydWU7KiIpICU+JSBzdHJfc3BsaXQoJzsnKSAlPiUgdW5saXN0ICU+JSB1bmlxdWUKCnZhcmtvZGVyX3Jlc3VsdHMgPSB2YXJrb2Rlcl9yZXN1bHRzICU+JQogIG11dGF0ZShxdWVyeV9sYWJlbHMgPSBzdHJfcmVtb3ZlKGFjdHVhbF9sYWJlbHMsIjsqbG93X3F1YWxpdHk6VHJ1ZTsqIikgJT4lIHN0cl9zcGxpdCgnOycpLAogICAgICAgICBwcmVkaWN0ZWRfbGlzdCA9IHN0cl9zcGxpdChwcmVkaWN0ZWRfbGFiZWxzLCc7JykKICAgICAgICAgKSAlPiUKICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKGZhbWlseV9jb3JyZWN0ID0gcXVlcnlfbGFiZWxzW3N0cl9kZXRlY3QocXVlcnlfbGFiZWxzLCdmYW1pbHknKV0gJWluJSBwcmVkaWN0ZWRfbGlzdCwKICAgICAgICAgZ2VudXNfY29ycmVjdCA9IHF1ZXJ5X2xhYmVsc1tzdHJfZGV0ZWN0KHF1ZXJ5X2xhYmVscywnZ2VudXMnKV0gJWluJSBwcmVkaWN0ZWRfbGlzdCwKICAgICAgICAgc3BlY2llc19jb3JyZWN0ID0gaWZlbHNlKGFueShzdHJfZGV0ZWN0KHF1ZXJ5X2xhYmVscywnc3BlY2llcycpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHF1ZXJ5X2xhYmVsc1tzdHJfZGV0ZWN0KHF1ZXJ5X2xhYmVscywnc3BlY2llcycpXSAlaW4lIHByZWRpY3RlZF9saXN0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTkEKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgIGZhbWlseV9pbmNvcnJlY3QgPSBhbnkoIShwcmVkaWN0ZWRfbGlzdFtzdHJfZGV0ZWN0KHByZWRpY3RlZF9saXN0LCdmYW1pbHknKV0gJWluJSBxdWVyeV9sYWJlbHNbc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsJ2ZhbWlseScpXSkpLAogICAgICAgICBnZW51c19pbmNvcnJlY3QgPSBhbnkoIShwcmVkaWN0ZWRfbGlzdFtzdHJfZGV0ZWN0KHByZWRpY3RlZF9saXN0LCdnZW51cycpXSAlaW4lIHF1ZXJ5X2xhYmVsc1tzdHJfZGV0ZWN0KHF1ZXJ5X2xhYmVscywnZ2VudXMnKV0pKSwKICAgICAgICAgc3BlY2llc19pbmNvcnJlY3QgPSBpZmVsc2UoYW55KHN0cl9kZXRlY3QocXVlcnlfbGFiZWxzLCdzcGVjaWVzJykpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYW55KCEocHJlZGljdGVkX2xpc3Rbc3RyX2RldGVjdChwcmVkaWN0ZWRfbGlzdCwnc3BlY2llcycpXSAlaW4lIHF1ZXJ5X2xhYmVsc1tzdHJfZGV0ZWN0KHF1ZXJ5X2xhYmVscywnc3BlY2llcycpXSkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTkEKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgCiAgICAgICAgICkKCnJldHVybih2YXJrb2Rlcl9yZXN1bHRzKQp9CmBgYAoKCmBgYHtyfQpzdW1tYXJpemVfcmVzdWx0cyA9IGZ1bmN0aW9uKHJlcyxsZXZlbCl7CiAgcmVzID0gcmVzICU+JQogICAgdW5ncm91cCgpICU+JQogICAgbXV0YXRlKGxvd19xdWFsaXR5ID0gc3RyX2RldGVjdChhY3R1YWxfbGFiZWxzLCJsb3dfcXVhbGl0eTpUcnVlIiksCiAgICAgICAgICAgcmVzdWx0ID0gYXMuY2hhcmFjdGVyKGlmZWxzZShyZXNbLHN0cl9jKGxldmVsLCdjb3JyZWN0JyxzZXA9J18nKV0gJiAhcmVzWyxzdHJfYyhsZXZlbCwnaW5jb3JyZWN0JyxzZXA9J18nKV0sICdjb3JyZWN0JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKHJlc1ssc3RyX2MobGV2ZWwsJ2NvcnJlY3QnLHNlcD0nXycpXSAmIHJlc1ssc3RyX2MobGV2ZWwsJ2luY29ycmVjdCcsc2VwPSdfJyldLCAnYW1iaWd1b3VzJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZSghcmVzWyxzdHJfYyhsZXZlbCwnY29ycmVjdCcsc2VwPSdfJyldICAmIHJlc1ssc3RyX2MobGV2ZWwsJ2luY29ycmVjdCcsc2VwPSdfJyldLCAnaW5jb3JyZWN0JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdpbmNvbmNsdXNpdmUnCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApKSkpCiAgICAgICAgICAgKSAlPiUKICAgIGZpbHRlcighaXMubmEocmVzdWx0KSkgJT4lCiAgICBncm91cF9ieShxdWVyeV9icCxyZXN1bHQpICU+JQogICAgc3VtbWFyaXNlKE49bigpLCAuZ3JvdXBzID0gJ2Ryb3AnKSAlPiUKICAgIGdyb3VwX2J5KHF1ZXJ5X2JwKSAlPiUKICAgIG11dGF0ZShwPSBOL3N1bShOKSkgJT4lCiAgICBtdXRhdGUocXVlcnlfYnAgPSBhcy5pbnRlZ2VyKHN0cl9yZW1vdmUocXVlcnlfYnAsJ0snKSkqMTAwMCkgJT4lCiAgICB1bmdyb3VwKCkgJT4lCiAgICBtdXRhdGUocXVlcnlfYnAgPSBhcy5mYWN0b3IocXVlcnlfYnApKSAlPiUKICAgIGNvbXBsZXRlKHF1ZXJ5X2JwLHJlc3VsdCwgZmlsbCA9IGxpc3QocCA9IDAsIE4gPSAwKSkgJT4lCiAgICBtdXRhdGUocXVlcnlfYnAgPSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihxdWVyeV9icCkpKSAlPiUKICAgIHVuZ3JvdXAoKQogICAgCiAgcmV0dXJuKHJlcykKfQpgYGAKCgpgYGB7cn0KcGxvdF9hcmVhID0gZnVuY3Rpb24oc3VtX2RmLCB0aXRsZSwgcmVsYXRpdmUgPSBGQUxTRSwgZ3JpZCA9IFRSVUUsIHhsaW1fYWxsID0gVFJVRSwgd3JhcCl7CiAgYnJlYWtzID0gYyg1MDAwMDAsCiAgICAgICAgICAgICAxMDAwMDAwLAogICAgICAgICAgICAgMjAwMDAwMCwKICAgICAgICAgICAgIDUwMDAwMDAsCiAgICAgICAgICAgICAxMDAwMDAwMCwKICAgICAgICAgICAgIDIwMDAwMDAwLAogICAgICAgICAgICAgNTAwMDAwMDAsCiAgICAgICAgICAgICAxMDAwMDAwMDAsCiAgICAgICAgICAgICAyMDAwMDAwMDAKICAgICAgICAgICAgICkKICBpZiAoeGxpbV9hbGwpewogICAgeGxpbWl0cyA9IHJhbmdlKGJyZWFrcykKICB9IGVsc2UgewogICAgeGxpbWl0cyA9IHJhbmdlKHN1bV9kZiRxdWVyeV9icCkKICB9CiAgCiAgCiAgc3VtX2RmID0gc3VtX2RmICU+JQogICAgbXV0YXRlKHJlc3VsdCA9IGZhY3RvcihyZXN1bHQsb3JkZXJlZCA9IFQsIGxldmVscyA9IGMoJ2NvcnJlY3QnLCdhbWJpZ3VvdXMnLCdpbmNvbmNsdXNpdmUnLCdpbmNvcnJlY3QnKSkpIAogIGlmIChyZWxhdGl2ZSl7CiAgICB5bGltaXRzID0gYygwLDEpCiAgfSBlbHNlIHsKICAgIHlsaW1pdHMgPSBjKDAsc3VtX2RmICU+JSBncm91cF9ieShxdWVyeV9icCkgJT4lIHN1bW1hcml6ZShOPXN1bShOKSkgJT4lIHB1bGwoTikgJT4lIG1heCkKICB9CiAgCiAgCiAgIyBHZXQgY29sb3JzIGZyb20gYSBDb2xvciBCcmV3ZXIgcGFsZXR0ZQogIGJyZXdlcl9jb2xvcnMgPC0gUkNvbG9yQnJld2VyOjpicmV3ZXIucGFsKDQsICJBY2NlbnQiKQogIAogIGlmIChyZWxhdGl2ZSkgewogICAgcDEgPSBnZ3Bsb3Qoc3VtX2RmLCBhZXMoeD1xdWVyeV9icCx5PXAsZmlsbD1yZXN1bHQpKSArCiAgICBnZW9tX2FyZWEocG9zaXRpb249J3N0YWNrJykgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gc2V0TmFtZXMoYnJld2VyX2NvbG9ycywgYygiY29ycmVjdCIsICJhbWJpZ3VvdXMiLCAiaW5jb25jbHVzaXZlIiwgImluY29ycmVjdCIpKSkgKwogICAgc2NhbGVfYWxwaGFfbWFudWFsKHZhbHVlcz1jKDAuNSwxKSkgKwogICAgc2NhbGVfeF9sb2cxMChsYWJlbHMgPSBzY2FsZXM6OmxhYmVsX251bWJlcihzY2FsZV9jdXQgPSBzY2FsZXM6OmN1dF9zaSgnYnAnKSksYnJlYWtzID0gYnJlYWtzKSAgKwogICAgc2NhbGVfeV9jb250aW51b3VzKCkgKwogICAgZ2d0aXRsZSh0aXRsZSkgKwogICAgeWxhYignRnJhY3Rpb24gb2Ygc2FtcGxlcycpICsKICAgIHhsYWIoJ0Jhc2UgcGFpcnMgaW4gcXVlcnkgaW1hZ2VzJykgKwogICAgdGhlbWVfZmV3KCkgKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoaGp1c3Q9MSxhbmdsZT00NSkpCiAgfSBlbHNlIHsKICAgICAgcDEgPSBnZ3Bsb3Qoc3VtX2RmLCBhZXMoeD1xdWVyeV9icCx5PU4sZmlsbD1yZXN1bHQpKSArCiAgICBnZW9tX2FyZWEocG9zaXRpb249J3N0YWNrJykgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gc2V0TmFtZXMoYnJld2VyX2NvbG9ycywgYygiY29ycmVjdCIsICJhbWJpZ3VvdXMiLCAiaW5jb25jbHVzaXZlIiwgImluY29ycmVjdCIpKSkgKwogICAgc2NhbGVfYWxwaGFfbWFudWFsKHZhbHVlcz1jKDAuNSwxKSkgKwogICAgc2NhbGVfeF9sb2cxMChsYWJlbHMgPSBzY2FsZXM6OmxhYmVsX251bWJlcihzY2FsZV9jdXQgPSBzY2FsZXM6OmN1dF9zaSgnYnAnKSksYnJlYWtzID0gYnJlYWtzKSAgICsKICAgIHNjYWxlX3lfY29udGludW91cygpICsKICAgIGdndGl0bGUodGl0bGUpICsKICAgIHlsYWIoJ051bWJlciBvZiBzYW1wbGVzJykgKwogICAgeGxhYignQmFzZSBwYWlycyBpbiBxdWVyeSBpbWFnZXMnKSArCiAgICB0aGVtZV9mZXcoKSArCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChoanVzdD0xLGFuZ2xlPTQ1KSkKICB9CiAgCiAgaWYgKGdyaWQpewogICAgcDEgPSBwMSArCiAgICAgIHNjYWxlX3lfY29udGludW91cyhuLmJyZWFrcyA9IDEwLCBtaW5vcl9icmVha3MgPSB3YWl2ZXIoKSkgKwogICAgICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSBOQSksCiAgICAgICAgICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSBncmF5KDAuNSkpLAogICAgICAgICAgICBwYW5lbC5ncmlkLm1pbm9yLnkgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gZ3JheSgwLjYpLGxpbmV0eXBlID0gMiksCiAgICAgICAgICAgIHBhbmVsLm9udG9wID0gVFJVRSkKICB9CiAgCiAgcDEgPSBwMSArIGNvb3JkX2NhcnRlc2lhbih4bGltPXhsaW1pdHMsIHlsaW09eWxpbWl0cyxleHBhbmQgPSBGQUxTRSkKICAKICBpZiAoIW1pc3Npbmcod3JhcCkpIHsKICAgIHAxID0gcDEgKyBmYWNldF93cmFwKGFzLmZvcm11bGEod3JhcCkpCiAgfQogIAogIHJldHVybihwMSkKfQogIApgYGAKCgpOb3cgbGV0J3MgcGxvdCBnZW51cy1sZXZlbCBhY2N1cmFjeSBmb3IgYSBtb2RlbCB0YWtpbmcgcXVhbGl0eSBsYWJlbHMgaW50byBhY2NvdW50OgoKCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KcmVzdWx0cyA9IHJlYWRfYW5kX3Byb2Nlc3NfeHZhbCgnTWFscGlnaGlhY2VhZStDaHJ5c29iYWxhbmFjZWFlL3ZhcktvZGVyL3ZpdF9yZXN1bHRzLycpCnN1bW1hcnlfZ2VudXMgPSBzdW1tYXJpemVfcmVzdWx0cyhyZXN1bHRzLCdnZW51cycpCnBfZ2VudXMgPSBwbG90X2FyZWEoc3VtbWFyeV9nZW51cywgJ3ZhcktvZGVyIGdlbnVzJywgcmVsYXRpdmUgPSBUUlVFKQpwX2dlbnVzCmBgYApOb3cgdGhlIHNhbWUgYnV0IHdpdGggc3BlY2llcwpgYGB7cn0Kc3VtbWFyeV9zcGVjaWVzID0gc3VtbWFyaXplX3Jlc3VsdHMocmVzdWx0cywnc3BlY2llcycpCnBfc3BlY2llcyA9IHBsb3RfYXJlYShzdW1tYXJ5X3NwZWNpZXMsICd2YXJLb2RlciBzcGVjaWVzJywgcmVsYXRpdmUgPSBUUlVFKQpwX3NwZWNpZXMKYGBgCgpGaW5hbGx5LCBmYW1pbHkKYGBge3J9CnN1bW1hcnlfZmFtaWx5ID0gc3VtbWFyaXplX3Jlc3VsdHMocmVzdWx0cywnZmFtaWx5JykKcF9mYW1pbHkgPSBwbG90X2FyZWEoc3VtbWFyeV9mYW1pbHksICd2YXJLb2RlciBmYW1pbHknLCByZWxhdGl2ZSA9IFRSVUUpCnBfZmFtaWx5CmBgYAojIyB3aGF0IGV4cGxhaW5zIHRoZSBlcnJvcnM/CgpOb3cgd2Ugd2lsbCB0cnkgdG8gaWRlbnRpZnkgd2hpY2ggc2FtcGxlcyBmYWlsZWQgYW5kIHdoeSB0aGV5IGZhaWxlZC4gUGFydGljdWFybHksIGhvdyBkbyAKRE5BIHF1YWxpdHksIGFtb3VudCBvZiBkYXRhLCBhbmQgdGhlIG51bWJlciBvZiBzYW1wbGVzIHBlciBjbGFzcyBpbXBhY3QgcmVzdWx0cz8gV2Ugd2lsbCB1c2UgZ2VudXMtbGV2ZWwgcHJlZGljdGlvbnMgdG8gdGVzdC4KCmBgYHtyfQpnZW51c19wcmVkaWN0aW9ucyA9IHJlc3VsdHMgJT4lCiAgbXV0YXRlKHByZWRpY3RlZF9nZW51cyA9IHN0cl9leHRyYWN0KHByZWRpY3RlZF9sYWJlbHMsICdnZW51czpbXjtdKicpLAogICAgICAgICBhY3R1YWxfZ2VudXMgPSBzdHJfZXh0cmFjdChhY3R1YWxfbGFiZWxzLCAnZ2VudXM6W147XSonKSkgJT4lCiAgc2VsZWN0KC1zdGFydHNfd2l0aCgnZmFtaWx5JyksLXN0YXJ0c193aXRoKCdzcGVjaWVzJykpICU+JQogIHBpdm90X2xvbmdlcihjb2xzID0gc3RhcnRzX3dpdGgoImdlbnVzIiksIG5hbWVzX3RvID0gInByZWRpY3RlZF9sYWJlbCIsIHZhbHVlc190byA9ICJjb25maWRlbmNlIikgJT4lCiAgZmlsdGVyKGFjdHVhbF9nZW51cyA9PSBwcmVkaWN0ZWRfbGFiZWwpICU+JQogIHNlbGVjdChxdWVyeV9icCwgc2FtcGxlX2lkLCBiYXNlZnJlcXVlbmN5X3NkLCBhY3R1YWxfZ2VudXMsIGNvbmZpZGVuY2UpICU+JQogIG11dGF0ZShxdWVyeV9icCA9IDEwMDAqKHN0cl9yZW1vdmUocXVlcnlfYnAsICJLIikgJT4lIGFzLmludGVnZXIpKQoKZ2VudXNfcHJlZGljdGlvbnMgPSBnZW51c19wcmVkaWN0aW9ucyAlPiUKICBzZWxlY3Qoc2FtcGxlX2lkLCBhY3R1YWxfZ2VudXMpICU+JQogIGRpc3RpbmN0KCkgJT4lCiAgZ3JvdXBfYnkoYWN0dWFsX2dlbnVzKSAlPiUKICBzdW1tYXJpc2UoTl9zYW1wbGVzID0gbigpKSAlPiUKICByaWdodF9qb2luKGdlbnVzX3ByZWRpY3Rpb25zKQoKZ2VudXNfcHJlZGljdGlvbnMKYGBgCk5vdyBsZXQncyBtYWtlIHNvbWUgcGxvdHMuIEZpcnN0LCB3aGF0IGlzIHRoZSBlZmZlY3Qgb2YgbnVtYmVyIG9mIHNhbXBsZXMgcGVyIGNsYXNzIGluIGNvbmZpZGVuY2U/CmBgYHtyfQpzZXQuc2VlZCgxMzIxNDUyNikKcGxvdF9nZW51c19OX3ZzX2NvbmYgPSBnZ3Bsb3QoZ2VudXNfcHJlZGljdGlvbnMsIGFlcyh4ID0gTl9zYW1wbGVzLTEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gY29uZmlkZW5jZSkpICsgCiAgc2NhbGVfY29sb3JfdmlyaWRpc19jKCkgKwogIGdlb21faml0dGVyKGFscGhhPTAuMykgKyAKICBzY2FsZV94X2xvZzEwKCkgKwogICN5bGFiKCdDb25maWRlbmNlIGluIGNvcnJlY3QgcHJlZGljdGlvblxuKGxvZ2l0IHNjYWxlKScpICsKICB5bGFiKCdDb25maWRlbmNlIGluIGNvcnJlY3QgcHJlZGljdGlvbicpICsKICB4bGFiKCdOdW1iZXIgb2Ygc2FtcGxlcyBpbiBjb3JyZWN0IGdlbnVzXG4obG9nIHNjYWxlKScpICsKICAjc2NhbGVfeV9jb250aW51b3VzKHRyYW5zID0gImxvZ2l0IiwgYnJlYWtzID0gYygxZS00LDAuMDAxLDAuMDEsMC4xLDAuMjUsMC41LDAuNzUsMC45LDAuOTksMC45OTksMS0xZS00KSkgKwogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHM9YygwLDEpKSArCiAgdGhlbWVfZmV3KCkgKwogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSBncmF5KDAuOCkpKQoKcGxvdF9nZW51c19OX3ZzX2NvbmYKYGBgCgpOb3csIHdoYXQgaXMgdGhlIGVmZmVjdCBvZiBzYW1wbGUgcXVhbGl0eSBpbiBjb25maWRlbmNlPwpgYGB7cn0Kc2V0LnNlZWQoMTMyMTQ1MjYpCnBsb3RfZ2VudXNfZnJlcXNkX3ZzX2NvbmYgPSBnZ3Bsb3QoZ2VudXNfcHJlZGljdGlvbnMsIGFlcyh4ID0gYmFzZWZyZXF1ZW5jeV9zZCwgeSA9IGNvbmZpZGVuY2UpKSArIAogIGdlb21fcG9pbnQoYWxwaGE9MC4zKSArIAogIHNjYWxlX3hfbG9nMTAoKSArCiAgI3NjYWxlX3lfY29udGludW91cyh0cmFucyA9ICJsb2dpdCIsIGJyZWFrcyA9IGMoMWUtNCwwLjAwMSwwLjAxLDAuMSwwLjI1LDAuNSwwLjc1LDAuOSwwLjk5LDAuOTk5LDEtMWUtNCkpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzPWMoMCwxKSkgKwogICN5bGFiKCdDb25maWRlbmNlIGluIGNvcnJlY3QgcHJlZGljdGlvblxuKGxvZ2l0IHNjYWxlKScpICsKICB5bGFiKCdDb25maWRlbmNlIGluIGNvcnJlY3QgcHJlZGljdGlvbicpICsKICB4bGFiKCdTdGFuZGFyZCBkZXZpYXRpb24gb2YgYmFzZSBmcmVxdWVuY2llcycpICsKICB0aGVtZV9mZXcoKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9saW5lKGNvbG91ciA9IGdyYXkoMC44KSkpCgpwbG90X2dlbnVzX2ZyZXFzZF92c19jb25mCmBgYAoKTm93LCB3aGF0IGlzIHRoZSBlZmZlY3Qgb2YgYW1vdW50IG9mIGRhdGEgaW4gY29uZmlkZW5jZT8KYGBge3J9CnNldC5zZWVkKDEzMjE0NTI2KQpwbG90X2dlbnVzX2JwX3ZzX2NvbmYgPSBnZ3Bsb3QoZ2VudXNfcHJlZGljdGlvbnMsIGFlcyh4ID0gcXVlcnlfYnAsIHkgPSBjb25maWRlbmNlKSkgKyAKICBnZW9tX2ppdHRlcihhbHBoYT0wLjMpICsgCiAgI3NjYWxlX3lfY29udGludW91cyh0cmFucyA9ICJsb2dpdCIsIGJyZWFrcyA9IGMoMWUtNCwwLjAwMSwwLjAxLDAuMSwwLjI1LDAuNSwwLjc1LDAuOSwwLjk5LDAuOTk5LDEtMWUtNCkpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzPWMoMCwxKSkgKwogICN5bGFiKCdDb25maWRlbmNlIGluIGNvcnJlY3QgcHJlZGljdGlvblxuKGxvZ2l0IHNjYWxlKScpICsKICB5bGFiKCdDb25maWRlbmNlIGluIGNvcnJlY3QgcHJlZGljdGlvbicpICsKICB4bGFiKCdCYXNlIHBhaXJzIGluIHF1ZXJ5IGltYWdlc1xuKGxvZyBzY2FsZSknKSArCiAgc2NhbGVfeF9sb2cxMCgpICsKICB0aGVtZV9mZXcoKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9saW5lKGNvbG91ciA9IGdyYXkoMC44KSkpCgpwbG90X2dlbnVzX2JwX3ZzX2NvbmYKYGBgCgpOb3cgbGV0J3Mgc2F2ZSB0aGUgdGhyZWUgb2YgdGhlbSBhcyBhIHNpbmdsZSBwbG90IHVzaW5nIGNvd3Bsb3QuCgpgYGB7cn0KY29tYmluZWRfY29uZiA9IHBhdGNod29yazo6d3JhcF9wbG90cyhwbG90X2dlbnVzX05fdnNfY29uZiArIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT04KSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxvdF9nZW51c19icF92c19jb25mICsgdGhlbWUoYXhpcy50aXRsZS55PWVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9OCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBsb3RfZ2VudXNfZnJlcXNkX3ZzX2NvbmYgKyB0aGVtZShheGlzLnRpdGxlLnk9ZWxlbWVudF9ibGFuaygpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9OCkpKSArCiAgcGF0Y2h3b3JrOjpwbG90X2Fubm90YXRpb24odGFnX2xldmVscyA9ICdBJykgCgpjb21iaW5lZF9jb25mCgpnZ3NhdmUoZmlsZW5hbWUgPSAnaW1hZ2VzX21hbnVzY3JpcHQvc3VwcF9jb25mX3ByZWRpY3RvcnMucGRmJyxkZXZpY2UgPSAncGRmJyx3aWR0aCA9IDcsaGVpZ2h0PTMsdW5pdHMgPSAnaW4nLHVzZURpbmdiYXRzPUYpCmBgYAoKCkxldCdzIHB1dCBpdCBhbGwgdG9nZXRoZXIgbm93IGluIGEgbGluZWFyIG1vZGVsOgoKYGBge3J9CmxtX2RhdGEgPSBnZW51c19wcmVkaWN0aW9ucyAlPiUKICBtdXRhdGUoY29uZmlkZW5jZSA9IGlmZWxzZShjb25maWRlbmNlID09IDEsIGNvbmZpZGVuY2UtMC4wMDAwMDAxLCBjb25maWRlbmNlKSwKICAgICAgICAgY29uZmlkZW5jZSA9IGNhcjo6bG9naXQoY29uZmlkZW5jZSkpICU+JQogIG11dGF0ZShxdWVyeV9icCA9IChxdWVyeV9icCAtIG1lYW4ocXVlcnlfYnApKS9zZChxdWVyeV9icCksCiAgICAgICAgIGJhc2VmcmVxdWVuY3lfc2QgPSAoYmFzZWZyZXF1ZW5jeV9zZCAtIG1lYW4oYmFzZWZyZXF1ZW5jeV9zZCkpL3NkKGJhc2VmcmVxdWVuY3lfc2QpLAogICAgICAgICBOX3NhbXBsZXMgPSAoTl9zYW1wbGVzIC0gbWVhbihOX3NhbXBsZXMpKS9zZChOX3NhbXBsZXMpCiAgICAgICAgICkgCgpmdWxsX21vZGVsID0gbG0oZm9ybXVsYSA9IGNvbmZpZGVuY2V+cXVlcnlfYnAqYmFzZWZyZXF1ZW5jeV9zZCpOX3NhbXBsZXMsIGRhdGEgPSBsbV9kYXRhKSAKZnVsbF9tb2RlbApzdW1tYXJ5KGZ1bGxfbW9kZWwpCnBsb3QoZnVsbF9tb2RlbCkKYGBgCgpgYGB7cn0KcmVkdWNlZF9tb2RlbCA9IHN0ZXAoZnVsbF9tb2RlbCwgZGlyZWN0aW9uID0iYm90aCIpCnJlZHVjZWRfbW9kZWwKc3VtbWFyeShyZWR1Y2VkX21vZGVsKQpwbG90KHJlZHVjZWRfbW9kZWwpCmBgYAoKCgoKCiMjIFNrbWVyCgpGb3Igc2ttZXIsIHdlIGxlZnQgZWFjaCBzYW1wbGUgb3V0LCBidWlsdCBhIHJlZmVyZW5jZSBhbmQgdGhlbiBxdWVyaWVkIHRoYXQgc2FtcGxlLiBXZSBoYXZlIHNldmVyYWwgZmlsZXMgaW4gd2hpY2ggcmVmZXJlbmNlIHNhbXBsZXMgYXJlIG9yZGVyZWQgYnkgdGhlaXIgZGlzdGFuY2UgdG8gdGhlIHF1ZXJ5LCB3ZSBoZXJlIHdlIHdpbGwgZXZhbHVhdGUgd2hldGhlciB0aGUgY2xvc2VzdCBzYW1wbGUgaXMgZnJvbSB0aGUgY29ycmVjdCBzcGVjaWVzIG9yIGdlbnVzLgoKQmVjYXVzZSBpdCBpcyBub3QgY2xlYXIgaG93IHNrbWVyIGJlaGF2ZXMgZm9yIGRpZmZlcmVudCBsZXZlbHMgb2YgY292ZXJhZ2UsIHdlIHJlcGVhdGVkIHRoaXMgZm9yIHNldmVyYWwgaW5wdXQgc2l6ZXMgKGluIG51bWJlciBvZiBiYXNlcGFpcnMpIGFzIHF1ZXJ5LCBidXQgYWx3YXlzIHVzZWQgdGhlIG1heGltdW0gaW5wdXQgZGl6ZSBhdmFpbGFibGUgKHVwIHRvIDIwME1iKSBmb3IgcmVmZXJlbmNlcy4KCkxldCdzIG1ha2UgYSBmdW5jdGlvbiB0aGF0IGV4dHJhY3RzIHRoZXNlIHJlc3VsdHMgYXMgYSB0YWJsZS4KCmBgYHtyfQoKc2FtcF9sYWJlbHMgPSByZXN1bHRzICU+JSBzZWxlY3Qoc2FtcGxlX2lkLGFjdHVhbF9sYWJlbHMpICU+JSBkaXN0aW5jdCgpCgpleHRyYWN0X3NrbWVyX3Jlc3VsdHMgPSBmdW5jdGlvbihmaWxlX3BhdGgpIHsKICAgICMgUmVhZCBvbmx5IHRoZSBmaXJzdCAyIGxpbmVzIG9mIHRoZSBmaWxlCiAgICBmaWxlX2xpbmVzIDwtIHJlYWRMaW5lcyhmaWxlX3BhdGgsIG4gPSAyKQogICAgCiAgICAjIEV4dHJhY3Qgc2FtcGxlX0lELCBiYXNlcGFpcnMgZnJvbSB0aGUgZmlyc3QgbGluZQogICAgc2FtcGxlX2luZm8gPC0gc3RyX21hdGNoKGZpbGVfbGluZXNbMV0sICJcXHMqKC4qPylAKFxcZCtLKSIpWywgMjozXQogICAgc2FtcGxlX0lEIDwtIHNhbXBsZV9pbmZvWzFdCiAgICBiYXNlcGFpcnMgPC0gc2FtcGxlX2luZm9bMl0KICAgIAogICAgIyBFeHRyYWN0IHJlZmVyZW5jZV9zYW1wbGVfSUQsIGRpc3RhbmNlIGZyb20gdGhlIHNlY29uZCBsaW5lCiAgICByZWZlcmVuY2VfaW5mbyA8LSBzdHJfbWF0Y2goZmlsZV9saW5lc1syXSwgIlxccyooLio/KUAuKlxccysoXFxkK1xcLlxcZCspIilbLCAyOjNdCiAgICByZWZlcmVuY2Vfc2FtcGxlX0lEIDwtIHJlZmVyZW5jZV9pbmZvWzFdCiAgICBkaXN0YW5jZSA8LSBhcy5udW1lcmljKHJlZmVyZW5jZV9pbmZvWzJdKQogICAgCiAgICAjIENyZWF0ZSBhIHRpYmJsZQogICAgdGliYmxlKAogICAgICAgIHNhbXBsZV9pZCA9IHNhbXBsZV9JRCwKICAgICAgICBxdWVyeV9icCA9IGJhc2VwYWlycywKICAgICAgICBjbG9zZXN0X3JlZmVyZW5jZV9zYW1wbGVfaWQgPSByZWZlcmVuY2Vfc2FtcGxlX0lELAogICAgICAgIGNsb3Nlc3RfZGlzdGFuY2UgPSBkaXN0YW5jZQogICAgKSAKfQpgYGAKCk5vdyB3ZSB3aWxsIGFwcGx5IHRoaXMgZnVuY3Rpb24gdG8gYWxsIHNrbWVyIG91dHB1dCBmaWxlcy4KCmBgYHtyfQpwbGFuKG11bHRpc2Vzc2lvbih3b3JrZXJzID0gMTIpKQpza21lcl9yZXN1bHRzX2RmID0gZnVycnI6OmZ1dHVyZV9tYXBfZGZyKAogIGxpc3QuZmlsZXMoJ01hbHBpZ2hpYWNlYWUrQ2hyeXNvYmFsYW5hY2VhZS9za21lci9za21lcl94dmFsX3Jlc3VsdHMvJywgZnVsbC5uYW1lcyA9IFQpLAogIH4gZXh0cmFjdF9za21lcl9yZXN1bHRzKC54KQopICU+JQogIGxlZnRfam9pbihzYW1wX2xhYmVscywgYnkgPSAnc2FtcGxlX2lkJykgJT4lCiAgbGVmdF9qb2luKAogICAgc2FtcF9sYWJlbHMgJT4lIHNlbGVjdCgKICAgICAgY2xvc2VzdF9yZWZlcmVuY2Vfc2FtcGxlX2lkID0gJ3NhbXBsZV9pZCcsCiAgICAgIHByZWRpY3RlZF9sYWJlbHMgPSBhY3R1YWxfbGFiZWxzCiAgICApLAogICAgYnkgPSAnY2xvc2VzdF9yZWZlcmVuY2Vfc2FtcGxlX2lkJwogICkgJT4lCiAgbXV0YXRlKAogICAgcXVlcnlfbGFiZWxzID0gc3RyX3JlbW92ZShhY3R1YWxfbGFiZWxzLCAiOypsb3dfcXVhbGl0eTpUcnVlOyoiKSAlPiUgc3RyX3NwbGl0KCc7JyksCiAgICBwcmVkaWN0ZWRfbGlzdCA9IHN0cl9zcGxpdChwcmVkaWN0ZWRfbGFiZWxzLCAnOycpCiAgKSAlPiUKICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKAogICAgZmFtaWx5X2NvcnJlY3QgPSBxdWVyeV9sYWJlbHNbc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsICdmYW1pbHknKV0gJWluJSBwcmVkaWN0ZWRfbGlzdCwKICAgIGdlbnVzX2NvcnJlY3QgPSBxdWVyeV9sYWJlbHNbc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsICdnZW51cycpXSAlaW4lIHByZWRpY3RlZF9saXN0LAogICAgc3BlY2llc19jb3JyZWN0ID0gaWZlbHNlKGFueShzdHJfZGV0ZWN0KAogICAgICBxdWVyeV9sYWJlbHMsICdzcGVjaWVzJwogICAgKSksCiAgICBxdWVyeV9sYWJlbHNbc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsICdzcGVjaWVzJyldICVpbiUgcHJlZGljdGVkX2xpc3QsCiAgICBOQSksCiAgICBmYW1pbHlfaW5jb3JyZWN0ID0gYW55KCEocHJlZGljdGVkX2xpc3Rbc3RyX2RldGVjdChwcmVkaWN0ZWRfbGlzdCwgJ2ZhbWlseScpXSAlaW4lIHF1ZXJ5X2xhYmVsc1tzdHJfZGV0ZWN0KHF1ZXJ5X2xhYmVscywgJ2ZhbWlseScpXSkpLAogICAgZ2VudXNfaW5jb3JyZWN0ID0gYW55KCEocHJlZGljdGVkX2xpc3Rbc3RyX2RldGVjdChwcmVkaWN0ZWRfbGlzdCwgJ2dlbnVzJyldICVpbiUgcXVlcnlfbGFiZWxzW3N0cl9kZXRlY3QocXVlcnlfbGFiZWxzLCAnZ2VudXMnKV0pKSwKICAgIHNwZWNpZXNfaW5jb3JyZWN0ID0gaWZlbHNlKGFueShzdHJfZGV0ZWN0KAogICAgICBxdWVyeV9sYWJlbHMsICdzcGVjaWVzJwogICAgKSksCiAgICBhbnkoISgKICAgICAgcHJlZGljdGVkX2xpc3Rbc3RyX2RldGVjdChwcmVkaWN0ZWRfbGlzdCwgJ3NwZWNpZXMnKV0gJWluJSBxdWVyeV9sYWJlbHNbc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsICdzcGVjaWVzJyldCiAgICApKSwKICAgIE5BKQogICAgCiAgKQpwbGFuKHNlcXVlbnRpYWwpCnNrbWVyX3Jlc3VsdHNfZGYKYGBgCk5vdyBsZXQncyBzdW1tYXJpemUgYW5kIHBsb3QgYnkgZ2VudXM6CgpgYGB7cn0Kc2ttZXJfc3VtbWFyeV9nZW51cyA9IHN1bW1hcml6ZV9yZXN1bHRzKHNrbWVyX3Jlc3VsdHNfZGYsJ2dlbnVzJykKcF9za21lcl9nZW51cyA9IHBsb3RfYXJlYShza21lcl9zdW1tYXJ5X2dlbnVzLCAnU2ttZXIgZ2VudXMnLCByZWxhdGl2ZSA9IFRSVUUpCnBfc2ttZXJfZ2VudXMKYGBgCk5vdyBieSBzcGVjaWVzLiBJbiBTa21lciwgdGhlcmUgaXMgbm8gaW5jb25jbHVzaXZlIHJlc3VsdDogaWYgdGhlcmUgaXMgbm8gY29ycmVjdCBzcGVjaWVzIHByZWRpY3Rpb24sIGl0IG1lYW5zIHRoYXQgYSBzYW1wbGUgd2FzIHByZWRpY3RlZCBpbiB0aGUgd3JvbmcgZ2VudXMgYW5kIHRoZXJlZm9yZSBpdCBpcyBpbmNvcnJlY3QKCmBgYHtyfQpza21lcl9zdW1tYXJ5X3NwZWNpZXMgPSBzdW1tYXJpemVfcmVzdWx0cyhza21lcl9yZXN1bHRzX2RmLCdzcGVjaWVzJykgJT4lCiAgbXV0YXRlKHJlc3VsdCA9IGlmZWxzZShyZXN1bHQgPT0gJ2NvcnJlY3QnLCAnY29ycmVjdCcsJ2luY29ycmVjdCcpKSAlPiUKICBncm91cF9ieShxdWVyeV9icCxyZXN1bHQpICU+JQogIHN1bW1hcmlzZV9hbGwoc3VtKQpwX3NrbWVyX3NwZWNpZXMgPSBwbG90X2FyZWEoc2ttZXJfc3VtbWFyeV9zcGVjaWVzLCAnU2ttZXIgc3BlY2llcycsIHJlbGF0aXZlID0gVFJVRSkKcF9za21lcl9zcGVjaWVzCmBgYAoKQW5kIG5vdyBieSBmYW1pbHk6CgpgYGB7cn0Kc2ttZXJfc3VtbWFyeV9mYW1pbHkgPSBzdW1tYXJpemVfcmVzdWx0cyhza21lcl9yZXN1bHRzX2RmLCdmYW1pbHknKQpza21lcl9zdW1tYXJ5X2ZhbWlseSAKcF9za21lcl9mYW1pbHkgPSBwbG90X2FyZWEoc2ttZXJfc3VtbWFyeV9mYW1pbHksICdTa21lciBmYW1pbHknLCByZWxhdGl2ZSA9IFRSVUUpCnBfc2ttZXJfZmFtaWx5CmBgYAoKIyBUcmFkaXRpb25hbCBiYXJjb2RlcwojIyBCTEFTVCBzaW5nbGUgZ2VuZQpMZXQncyBub3cgcmVhZCB0aGUgdHJhZGl0aW9uYWwgYmFyY29kZSBCTEFTVCByZXN1bHRzIGFuZCBzdW1tYXJpemUgdGhlbSBpbiB0aGUgc2FtZSB3YXkgYXMgc2ttZXIgYW5kIHZhcktvZGVyLiBMZXQncyBzdGFydCBieSBkZWZpbmluZyBhIGZ1Y3Rpb24gdGhhdCByZWFkcyB0aGUgZGF0YSBzbyB3ZSBjYW4gc3VtbWFyaXplIGl0IHVzaW5nIHRoZSBwcmV2aW91c2x5IGRlZmluZWQgZnVuY3Rpb25zLgoKYGBge3J9CnJlYWRfdHJhZGl0aW9uYWxfYmFyY29kZXMgPSBmdW5jdGlvbihicCkgewogIGlucHV0X2ZpbGUgPSBwYXN0ZTAoCiAgICAnTWFscGlnaGlhY2VhZStDaHJ5c29iYWxhbmFjZWFlL3RyYWRpdGlvbmFsX2JhcmNvZGVzLzJfYmxhc3RfcGh5bG9nZW55X3Jlc3VsdC9HZW51cy8nLAogICAgYnAsCiAgICAnTV9ibGFzdF9waHlsb19zdW1fc3AudHN2JwogICkKICAKICBiYXJjb2RlX3JlcyA9IHJlYWRfZGVsaW0oaW5wdXRfZmlsZSkgJT4lCiAgICBwaXZvdF9sb25nZXIoLXNwLCBuYW1lc190byA9ICdtYXJrZXInLCB2YWx1ZXNfdG8gPSAnY2xvc2VzdF9yZWZlcmVuY2Vfc2FtcGxlX2lkJykgJT4lCiAgICByZW5hbWUoc2FtcGxlX2lkID0gJ3NwJykgJT4lCiAgICBtdXRhdGUoCiAgICAgIHNhbXBsZV9pZCA9IHN0cl9yZW1vdmVfYWxsKHNhbXBsZV9pZCwgJ0AuKycpLAogICAgICBjbG9zZXN0X3JlZmVyZW5jZV9zYW1wbGVfaWQgPSBzdHJfcmVtb3ZlX2FsbChjbG9zZXN0X3JlZmVyZW5jZV9zYW1wbGVfaWQsICdALisnKSwKICAgICAgcHJlZGljdGVkX2xhYmVscyA9IHNhbXBfbGFiZWxzJGFjdHVhbF9sYWJlbHNbbWF0Y2goY2xvc2VzdF9yZWZlcmVuY2Vfc2FtcGxlX2lkLCBzYW1wX2xhYmVscyRzYW1wbGVfaWQpXSwKICAgICAgYWN0dWFsX2xhYmVscyA9IHNhbXBfbGFiZWxzJGFjdHVhbF9sYWJlbHNbbWF0Y2goc2FtcGxlX2lkLCBzYW1wX2xhYmVscyRzYW1wbGVfaWQpXQogICAgKSAlPiUKICAgIGZpbHRlcihtYXJrZXIgIT0gJ0NvbmNhdGVuYXRlZF9waHlsb2dlbnknKSAlPiUKICAgIG11dGF0ZSgKICAgICAgcXVlcnlfbGFiZWxzID0gc3RyX3JlbW92ZShhY3R1YWxfbGFiZWxzLCAiOypsb3dfcXVhbGl0eTpUcnVlOyoiKSAlPiUgc3RyX3NwbGl0KCc7JyksCiAgICAgIHByZWRpY3RlZF9saXN0ID0gc3RyX3NwbGl0KHByZWRpY3RlZF9sYWJlbHMsICc7JykKICAgICkgJT4lCiAgICByb3d3aXNlKCkgJT4lCiAgICBtdXRhdGUoCiAgICAgIGZhbWlseV9jb3JyZWN0ID0gcXVlcnlfbGFiZWxzW3N0cl9kZXRlY3QocXVlcnlfbGFiZWxzLCAnZmFtaWx5JyldICVpbiUgcHJlZGljdGVkX2xpc3QsCiAgICAgIGdlbnVzX2NvcnJlY3QgPSBxdWVyeV9sYWJlbHNbc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsICdnZW51cycpXSAlaW4lIHByZWRpY3RlZF9saXN0LAogICAgICBzcGVjaWVzX2NvcnJlY3QgPSBpZmVsc2UoYW55KHN0cl9kZXRlY3QoCiAgICAgICAgcXVlcnlfbGFiZWxzLCAnc3BlY2llcycKICAgICAgKSksCiAgICAgIHF1ZXJ5X2xhYmVsc1tzdHJfZGV0ZWN0KHF1ZXJ5X2xhYmVscywgJ3NwZWNpZXMnKV0gJWluJSBwcmVkaWN0ZWRfbGlzdCwKICAgICAgTkEpLAogICAgICBmYW1pbHlfaW5jb3JyZWN0ID0gYW55KCEocHJlZGljdGVkX2xpc3Rbc3RyX2RldGVjdChwcmVkaWN0ZWRfbGlzdCwgJ2ZhbWlseScpXSAlaW4lIHF1ZXJ5X2xhYmVsc1tzdHJfZGV0ZWN0KHF1ZXJ5X2xhYmVscywgJ2ZhbWlseScpXSkpLAogICAgICBnZW51c19pbmNvcnJlY3QgPSBhbnkoIShwcmVkaWN0ZWRfbGlzdFtzdHJfZGV0ZWN0KHByZWRpY3RlZF9saXN0LCAnZ2VudXMnKV0gJWluJSBxdWVyeV9sYWJlbHNbc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsICdnZW51cycpXSkpLAogICAgICBzcGVjaWVzX2luY29ycmVjdCA9IGlmZWxzZShhbnkoc3RyX2RldGVjdCgKICAgICAgICBxdWVyeV9sYWJlbHMsICdzcGVjaWVzJwogICAgICApKSwKICAgICAgYW55KCEoCiAgICAgICAgcHJlZGljdGVkX2xpc3Rbc3RyX2RldGVjdChwcmVkaWN0ZWRfbGlzdCwgJ3NwZWNpZXMnKV0gJWluJSBxdWVyeV9sYWJlbHNbc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsICdzcGVjaWVzJyldCiAgICAgICkpLAogICAgICBOQSkKICAgICkgJT4lCiAgICBtdXRhdGVfYXQodmFycyhlbmRzX3dpdGgoIl9jb3JyZWN0IiksIGVuZHNfd2l0aCgiX2luY29ycmVjdCIpKSwKICAgICAgICAgICAgICB+IGlmZWxzZShpcy5uYShwcmVkaWN0ZWRfbGFiZWxzKSAmICFpcy5uYSguKSwgRkFMU0UsIC4pKSAlPiUKICAgIG11dGF0ZShxdWVyeV9icCA9IGJwICogMWUzKQogIAogIHJldHVybihiYXJjb2RlX3JlcykKfQpgYGAKCgpOb3cgd2UgY2FuIGFwcGx5IHRoaXMgZnVuY3Rpb24gdG8gYWxsIG9mIG91ciByZXN1bHRzOgoKYGBge3J9CnJlc3VsdHNfYmFyY29kZXMgPSBwdXJycjo6bWFwX2RmcihjKDAuNSwxLDIsNSwxMCwyMCw1MCwxMDAsMjAwKSxyZWFkX3RyYWRpdGlvbmFsX2JhcmNvZGVzKQpyZXN1bHRzX2JhcmNvZGVzCmBgYAoKTm93IGxldCdzIHN1bW1hcmlzZSBmb3IgZWFjaCBtYXJrZXIgc2VwYXJhdGVseToKYGBge3J9CmJhcmNvZGVfc3VtbWFyeV9mYW1pbHkgPSBzcGxpdChyZXN1bHRzX2JhcmNvZGVzLHJlc3VsdHNfYmFyY29kZXMkbWFya2VyKSAlPiUKICBwdXJycjo6bWFwX2Rmcih+c3VtbWFyaXplX3Jlc3VsdHMoLngsJ2ZhbWlseScpLC5pZD0nbWFya2VyJykKCmJhcmNvZGVfc3VtbWFyeV9mYW1pbHkKYGBgCgpgYGB7cn0KYmFyY29kZV9zdW1tYXJ5X2dlbnVzID0gc3BsaXQocmVzdWx0c19iYXJjb2RlcyxyZXN1bHRzX2JhcmNvZGVzJG1hcmtlcikgJT4lCiAgcHVycnI6Om1hcF9kZnIofnN1bW1hcml6ZV9yZXN1bHRzKC54LCdnZW51cycpLC5pZD0nbWFya2VyJykKCmJhcmNvZGVfc3VtbWFyeV9nZW51cwpgYGAKCmBgYHtyfQpiYXJjb2RlX3N1bW1hcnlfc3BlY2llcyA9IHNwbGl0KHJlc3VsdHNfYmFyY29kZXMscmVzdWx0c19iYXJjb2RlcyRtYXJrZXIpICU+JQogIHB1cnJyOjptYXBfZGZyKH5zdW1tYXJpemVfcmVzdWx0cygueCwnc3BlY2llcycpLC5pZD0nbWFya2VyJykKCmJhcmNvZGVfc3VtbWFyeV9zcGVjaWVzCmBgYAoKTm93IGxldCdzIHBsb3QsIG1ha2luZyBzZXBhcmF0ZSBwbG90cyBmb3IgZWFjaCBtYXJrZXI6CgpTcGVjaWVzOgpgYGB7cn0KcF9iYXJjb2RlX3NwZWNpZXMgPSBiYXJjb2RlX3N1bW1hcnlfc3BlY2llcyAlPiUKICBzcGxpdChiYXJjb2RlX3N1bW1hcnlfc3BlY2llcyRtYXJrZXIpICU+JQogIHB1cnJyOjptYXAofnBsb3RfYXJlYSgueCxwYXN0ZTAodW5pcXVlKC54JG1hcmtlciksJyBzcGVjaWVzJyksIHJlbGF0aXZlID0gVFJVRSwgeGxpbV9hbGwgPSBUUlVFKSkKCnBfYmFyY29kZV9zcGVjaWVzCmBgYApHZW5lcmE6CmBgYHtyfQpwX2JhcmNvZGVfZ2VudXMgPSBiYXJjb2RlX3N1bW1hcnlfZ2VudXMgJT4lCiAgc3BsaXQoYmFyY29kZV9zdW1tYXJ5X2dlbnVzJG1hcmtlcikgJT4lCiAgcHVycnI6Om1hcCh+cGxvdF9hcmVhKC54LHBhc3RlMCh1bmlxdWUoLngkbWFya2VyKSwnIGdlbnVzJyksIHJlbGF0aXZlID0gVFJVRSwgeGxpbV9hbGwgPSBUUlVFKSkKCnBfYmFyY29kZV9nZW51cwpgYGAKRmFtaWx5OgpgYGB7cn0KcF9iYXJjb2RlX2ZhbWlseSA9IGJhcmNvZGVfc3VtbWFyeV9mYW1pbHkgJT4lCiAgc3BsaXQoYmFyY29kZV9zdW1tYXJ5X2ZhbWlseSRtYXJrZXIpICU+JQogIHB1cnJyOjptYXAofnBsb3RfYXJlYSgueCxwYXN0ZTAodW5pcXVlKC54JG1hcmtlciksJyBmYW1pbHknKSwgcmVsYXRpdmUgPSBUUlVFLHhsaW1fYWxsID0gVFJVRSkpCgpwX2JhcmNvZGVfZmFtaWx5CmBgYAoKIyMgQ29uY2F0ZW5hdGVkIHRyZWUKTm93IHdlIHdpbGwgZG8gdGhlIHNhbWUgZm9yIGNvbmNhdGVuYXRlZCB0cmVlLiBMZXQncyBzdGFydCBieSBkZWZpbmluZyBhIGZ1bmN0aW9uIHRvIGdhdGhlciByZXN1bHRzLiBXZSB3aWxsIGNvbnNpZGVyIGEgcmVzdWx0IGFzIGNvcnJlY3QgaWYgdGhlIG1ham9yaXR5IG9mIHRoZSBzaXN0ZXIgdGF4b24gdG8gYSB0aXAgaGFzIHRoZSBzYW1lIGxhYmVsLgoKCmBgYHtyfQoKcmVhZF9jb25jYXRlbmF0ZWRfdHJlZV9yZXN1bHRzID0gZnVuY3Rpb24oYnApewogIAogIAojIFJlYWQgaW4geW91ciB0cmVlIC0gcmVwbGFjZSAneW91cl90cmVlX2ZpbGUubndrJyB3aXRoIHRoZSBwYXRoIHRvIHlvdXIgdHJlZSBmaWxlCnRyZWUgPSByZWFkLnRyZWUocGFzdGUwKCdNYWxwaWdoaWFjZWFlK0Nocnlzb2JhbGFuYWNlYWUvdHJhZGl0aW9uYWxfYmFyY29kZXMvMl9ibGFzdF9waHlsb2dlbnlfcmVzdWx0L0dlbnVzL2NvbmMuJyxicCwnbS5zcG5hbWUudHJlJykpCgojbGVhdmUgb25seSBzYW1wbGUgSURzIGFzIHRpcCBsYWJlbHMKdHJlZSR0aXAubGFiZWwgPSB0cmVlJHRpcC5sYWJlbCAlPiUgc3RyX3JlbW92ZSgiLipAIikgJT4lIHN0cl9yZW1vdmUoIiciKSAlPiUgc3RyX3JlcGxhY2UoJyByZWYnLCdfcmVmJykKCiMgQ29tcHV0ZSB0aGUgcGF0cmlzdGljIGRpc3RhbmNlcyBhbmQgbGlzdCBhbGwgcmVmZXJlbmNlIG5hbWVzCnBhdHJpc3RpY19kaXN0YW5jZXMgPC0gY29waGVuZXRpYyh0cmVlKQphbGxfcmVmX25hbWVzID0gZGltbmFtZXMocGF0cmlzdGljX2Rpc3RhbmNlcylbWzFdXVtzdHJfZGV0ZWN0KGRpbW5hbWVzKHBhdHJpc3RpY19kaXN0YW5jZXMpW1sxXV0sJ19yZWYkJyldCmFsbF9ub25yZWYgPSBkaW1uYW1lcyhwYXRyaXN0aWNfZGlzdGFuY2VzKVtbMV1dW3N0cl9kZXRlY3QoZGltbmFtZXMocGF0cmlzdGljX2Rpc3RhbmNlcylbWzFdXSwnX3JlZiQnLG5lZ2F0ZSA9IFRSVUUpXQoKIyBGb3IgZWFjaCB0aXAsIGZpbmQgdGhlIHJlZmVyZW5jZSBzYW1wbGUgd2l0aCBjbG9zZXN0IHBhdHJpc3RpYyBkaXN0YW5jZQpmaW5kX2Nsb3Nlc3QgPSBmdW5jdGlvbih0aXApewogIHRvX2tlZXAgPSBjKHRpcCxhbGxfcmVmX25hbWVzW3N0cl9kZXRlY3QoYWxsX3JlZl9uYW1lcyxwYXN0ZTAodGlwLCdfcmVmJyksbmVnYXRlID0gVFJVRSldKQogIHJldHVybihuYW1lcyhzb3J0KHBhdHJpc3RpY19kaXN0YW5jZXNbdGlwLHRvX2tlZXBdKVsyXSkgJT4lCiAgICAgICAgICAgc3RyX3JlbW92ZSgnX3JlZicpKQp9CgpjbG9zZXN0X21hdGNoID0gcHVycnI6Om1hcF9jaHIoYWxsX25vbnJlZixmaW5kX2Nsb3Nlc3QpCgpzYW1wbGVzX3dpdGhfZGF0YSA9IHJlYWRfZGVsaW0ocGFzdGUwKCdNYWxwaWdoaWFjZWFlK0Nocnlzb2JhbGFuYWNlYWUvdHJhZGl0aW9uYWxfYmFyY29kZXMvMl9ibGFzdF9waHlsb2dlbnlfcmVzdWx0L0dlbnVzLycsYnAsJ01fYmxhc3RfcGh5bG9fc3VtX3NwLnRzdicpKSAlPiUgCiAgc2VsZWN0KHNhbXBsZV9pZD1zcCkgJT4lCiAgbXV0YXRlKHNhbXBsZV9pZCA9IHN0cl9yZW1vdmVfYWxsKHNhbXBsZV9pZCwgJ0AuKycpKQoKYmFyY29kZV9yZXMgPSB0aWJibGUoc2FtcGxlX2lkID0gYWxsX25vbnJlZiwKICAgICAgIGNsb3Nlc3RfcmVmZXJlbmNlX3NhbXBsZV9pZCA9IGNsb3Nlc3RfbWF0Y2gpICU+JQogIHJpZ2h0X2pvaW4oc2FtcGxlc193aXRoX2RhdGEpICU+JQogIG11dGF0ZSgKICAgICAgcHJlZGljdGVkX2xhYmVscyA9IHNhbXBfbGFiZWxzJGFjdHVhbF9sYWJlbHNbbWF0Y2goY2xvc2VzdF9yZWZlcmVuY2Vfc2FtcGxlX2lkLCBzYW1wX2xhYmVscyRzYW1wbGVfaWQpXSwKICAgICAgYWN0dWFsX2xhYmVscyA9IHNhbXBfbGFiZWxzJGFjdHVhbF9sYWJlbHNbbWF0Y2goc2FtcGxlX2lkLCBzYW1wX2xhYmVscyRzYW1wbGVfaWQpXQogICAgKSAlPiUKICBmaWx0ZXIoc2FtcGxlX2lkIT0nMjA5NScpICU+JQogIG11dGF0ZSgKICAgICAgcXVlcnlfbGFiZWxzID0gc3RyX3JlbW92ZShhY3R1YWxfbGFiZWxzLCAiOypsb3dfcXVhbGl0eTpUcnVlOyoiKSAlPiUgc3RyX3NwbGl0KCc7JyksCiAgICAgIHByZWRpY3RlZF9saXN0ID0gc3RyX3NwbGl0KHByZWRpY3RlZF9sYWJlbHMsICc7JykKICAgICkgJT4lCiAgICByb3d3aXNlKCkgJT4lCiAgICBtdXRhdGUoCiAgICAgIGZhbWlseV9jb3JyZWN0ID0gcXVlcnlfbGFiZWxzW3N0cl9kZXRlY3QocXVlcnlfbGFiZWxzLCAnZmFtaWx5JyldICVpbiUgcHJlZGljdGVkX2xpc3QsCiAgICAgIGdlbnVzX2NvcnJlY3QgPSBxdWVyeV9sYWJlbHNbc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsICdnZW51cycpXSAlaW4lIHByZWRpY3RlZF9saXN0LAogICAgICBzcGVjaWVzX2NvcnJlY3QgPSBpZmVsc2UoYW55KHN0cl9kZXRlY3QoCiAgICAgICAgcXVlcnlfbGFiZWxzLCAnc3BlY2llcycKICAgICAgKSksCiAgICAgIHF1ZXJ5X2xhYmVsc1tzdHJfZGV0ZWN0KHF1ZXJ5X2xhYmVscywgJ3NwZWNpZXMnKV0gJWluJSBwcmVkaWN0ZWRfbGlzdCwKICAgICAgTkEpLAogICAgICBmYW1pbHlfaW5jb3JyZWN0ID0gYW55KCEocHJlZGljdGVkX2xpc3Rbc3RyX2RldGVjdChwcmVkaWN0ZWRfbGlzdCwgJ2ZhbWlseScpXSAlaW4lIHF1ZXJ5X2xhYmVsc1tzdHJfZGV0ZWN0KHF1ZXJ5X2xhYmVscywgJ2ZhbWlseScpXSkpLAogICAgICBnZW51c19pbmNvcnJlY3QgPSBhbnkoIShwcmVkaWN0ZWRfbGlzdFtzdHJfZGV0ZWN0KHByZWRpY3RlZF9saXN0LCAnZ2VudXMnKV0gJWluJSBxdWVyeV9sYWJlbHNbc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsICdnZW51cycpXSkpLAogICAgICBzcGVjaWVzX2luY29ycmVjdCA9IGlmZWxzZShhbnkoc3RyX2RldGVjdCgKICAgICAgICBxdWVyeV9sYWJlbHMsICdzcGVjaWVzJwogICAgICApKSwKICAgICAgYW55KCEoCiAgICAgICAgcHJlZGljdGVkX2xpc3Rbc3RyX2RldGVjdChwcmVkaWN0ZWRfbGlzdCwgJ3NwZWNpZXMnKV0gJWluJSBxdWVyeV9sYWJlbHNbc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsICdzcGVjaWVzJyldCiAgICAgICkpLAogICAgICBOQSkKICAgICkgJT4lCiAgICBtdXRhdGVfYXQodmFycyhlbmRzX3dpdGgoIl9jb3JyZWN0IiksIGVuZHNfd2l0aCgiX2luY29ycmVjdCIpKSwKICAgICAgICAgICAgICB+IGlmZWxzZShpcy5uYShwcmVkaWN0ZWRfbGFiZWxzKSAmICFpcy5uYSguKSwgRkFMU0UsIC4pKSAlPiUKICAgIG11dGF0ZShxdWVyeV9icCA9IGJwICogMWUzKQogIAogIHJldHVybihiYXJjb2RlX3JlcykKfQoKYGBgCgpOb3cgbGV0J3MgYXBwbHkgdGhpcyBmdW5jdGlvbgpgYGB7cn0KcmVzdWx0c19jb25jYXRfYmFyY29kZXMgPSBwdXJycjo6bWFwX2RmcihjKDAuNSwxLDIsNSwxMCwyMCw1MCwxMDAsMjAwKSxyZWFkX2NvbmNhdGVuYXRlZF90cmVlX3Jlc3VsdHMpCnJlc3VsdHNfY29uY2F0X2JhcmNvZGVzCmBgYApMZXQncyBzdW1tYXJpemUgcmVzdWx0cyBhbmQgcGxvdCBmb3IgZ2VudXMsIHNwZWNpZXMgYW5kIGZhbWlseSBhY2N1cmFjeQoKYGBge3J9CmNvbmNhdF9zdW1tYXJ5X3NwZWNpZXMgPSBzdW1tYXJpemVfcmVzdWx0cyhyZXN1bHRzX2NvbmNhdF9iYXJjb2Rlcywnc3BlY2llcycpCnBfY29uY2F0X3NwZWNpZXMgPSBwbG90X2FyZWEoY29uY2F0X3N1bW1hcnlfc3BlY2llcywgcmVsYXRpdmUgPSBGQUxTRSx0aXRsZSA9ICdDb25jYXRlbmF0ZWQgYmFyY29kZXMgc3BlY2llcycseGxpbV9hbGwgPSBUUlVFKQpwX2NvbmNhdF9zcGVjaWVzCmBgYApgYGB7cn0KY29uY2F0X3N1bW1hcnlfZ2VudXMgPSBzdW1tYXJpemVfcmVzdWx0cyhyZXN1bHRzX2NvbmNhdF9iYXJjb2RlcywnZ2VudXMnKQpwX2NvbmNhdF9nZW51cyA9IHBsb3RfYXJlYShjb25jYXRfc3VtbWFyeV9nZW51cywgcmVsYXRpdmUgPSBUUlVFLHRpdGxlID0gJ0NvbmNhdGVuYXRlZCBiYXJjb2RlcyBnZW51cycseGxpbV9hbGwgPSBUUlVFKQpwX2NvbmNhdF9nZW51cwpgYGAKCmBgYHtyfQpjb25jYXRfc3VtbWFyeV9mYW1pbHkgPSBzdW1tYXJpemVfcmVzdWx0cyhyZXN1bHRzX2NvbmNhdF9iYXJjb2RlcywnZmFtaWx5JykKcF9jb25jYXRfZmFtaWx5ID0gcGxvdF9hcmVhKGNvbmNhdF9zdW1tYXJ5X2ZhbWlseSwgcmVsYXRpdmUgPSBUUlVFLHRpdGxlID0gJ0NvbmNhdGVuYXRlZCBiYXJjb2RlcyBmYW1pbHknLHhsaW1fYWxsID0gVFJVRSkKcF9jb25jYXRfZmFtaWx5CmBgYAoKCiMgRGlyZWN0IGNvbXBhcmlzb24KCk5vdyBsZXQncyBjb21wYXJlIG1ldGhvZHMgc2lkZSBieSBzaWRlLiBGb3IgZ2VudXMgbGV2ZWw6CmBgYHtyIGZpZy5oZWlnaHQ9MTB9CnAgPSBwYXRjaHdvcms6OndyYXBfcGxvdHMocF9nZW51cyArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpKSwgCiAgICAgICAgICAgICAgICAgICBwX3NrbWVyX2dlbnVzICsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCkpLAogICAgICAgICAgICAgICAgICAgcF9iYXJjb2RlX2dlbnVzJElUUyArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpKSwKICAgICAgICAgICAgICAgICAgIHBfYmFyY29kZV9nZW51cyRyYmNMICsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCkpLAogICAgICAgICAgICAgICAgICAgcF9jb25jYXRfZ2VudXMsCiAgICAgICAgICAgICAgICAgICBuY29sID0gMSkgKwogIHBsb3RfYW5ub3RhdGlvbih0aXRsZSA9ICdHZW51cy1sZXZlbCBhY2N1cmFjeScpCnAKZ2dzYXZlKCdpbWFnZXNfbWFudXNjcmlwdC9maWczX2dlbnVzX2FjY3VyYWN5LnBkZicsIHdpZHRoPTUsaGVpZ2h0ID0gMTApCmdnc2F2ZSgnaW1hZ2VzX21hbnVzY3JpcHQvZmlnM19nZW51c19hY2N1cmFjeS5wbmcnLCB3aWR0aD01LGhlaWdodCA9IDEwLGRwaT0xMjAwKQpgYGAKTm93IGZvciBzcGVjaWVzIGxldmVsOgpgYGB7ciBmaWcuaGVpZ2h0ID0gMTB9CnAgPSBwYXRjaHdvcms6OndyYXBfcGxvdHMocF9zcGVjaWVzICsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCkpLCAKICAgICAgICAgICAgICAgICAgIHBfc2ttZXJfc3BlY2llcyArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpKSwKICAgICAgICAgICAgICAgICAgIHBfYmFyY29kZV9zcGVjaWVzJElUUyArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpKSwKICAgICAgICAgICAgICAgICAgIHBfYmFyY29kZV9zcGVjaWVzJHJiY0wgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSksCiAgICAgICAgICAgICAgICAgICBwX2NvbmNhdF9zcGVjaWVzLAogICAgICAgICAgICAgICAgICAgbmNvbCA9IDEpICsKICBwbG90X2Fubm90YXRpb24odGl0bGUgPSAnc3BlY2llcy1sZXZlbCBhY2N1cmFjeScpCnAKZ2dzYXZlKCdpbWFnZXNfbWFudXNjcmlwdC9maWczX3NwZWNpZXNfYWNjdXJhY3kucGRmJywgd2lkdGg9NSxoZWlnaHQgPSAxMCkKZ2dzYXZlKCdpbWFnZXNfbWFudXNjcmlwdC9maWczX3NwZWNpZXNfYWNjdXJhY3kucG5nJywgd2lkdGg9NSxoZWlnaHQgPSAxMCxkcGk9MTIwMCkKYGBgCk5vdyBmb3IgZmFtaWx5IGxldmVsOgpgYGB7ciBmaWcuaGVpZ2h0ID0gMTB9CnAgPSBwYXRjaHdvcms6OndyYXBfcGxvdHMocF9mYW1pbHkgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSksIAogICAgICAgICAgICAgICAgICAgcF9za21lcl9mYW1pbHkgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSksCiAgICAgICAgICAgICAgICAgICBwX2JhcmNvZGVfZmFtaWx5JElUUyArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpKSwKICAgICAgICAgICAgICAgICAgIHBfYmFyY29kZV9mYW1pbHkkcmJjTCArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpKSwKICAgICAgICAgICAgICAgICAgIHBfY29uY2F0X2ZhbWlseSwKICAgICAgICAgICAgICAgICAgIG5jb2wgPSAxKSArCiAgcGxvdF9hbm5vdGF0aW9uKHRpdGxlID0gJ2ZhbWlseS1sZXZlbCBhY2N1cmFjeScpCnAKZ2dzYXZlKCdpbWFnZXNfbWFudXNjcmlwdC9maWczX2ZhbWlseV9hY2N1cmFjeS5wZGYnLCB3aWR0aD01LGhlaWdodCA9IDEwKQpnZ3NhdmUoJ2ltYWdlc19tYW51c2NyaXB0L2ZpZzNfZmFtaWx5X2FjY3VyYWN5LnBuZycsIHdpZHRoPTUsaGVpZ2h0ID0gMTAsZHBpPTEyMDApCmBgYAoKIyBDb21wYXJpc29uIG9mIHJ1biB0aW1lcwoKTm93IGxldCdzIGNvbXBhcmUgdGhlIHRpbWUgdG8gcHJvZHVjZSByZWZlcmVuY2VzIGFuZCB0byBwcm9kdWNlIAoKIyBTUkEKCkZpbmFsbHksIGxldCdzIHN1bW1hcml6ZSByZXN1bHRzIGZvciB0aGUgd2hvbGUgU1JBIGRhdGFzZXQuIEluIHRoaXMgY2FzZSwgd2Ugb25seSBoYXZlIHZhcktvZGVyIHNpbmNlIFNrbWVyIGNhbm5vdCBmaW5pc2ggYW5kIHRyYWRpdGlvbmFsIGJhcmNvZGVzIGFyZSBpbmFwcGxpY2FibGUuCgpgYGB7cn0KdmFyS29kZXJfU1JBX3Jlc3VsdHMgID0gcmVhZF9jc3YoJ2FsbF9TUkEvdmFya29kZXJfcXVlcnlfcmVzdWx0cy9wcmVkaWN0aW9ucy5jc3YnKSAlPiUKc2VsZWN0KC0xKSAlPiUKICBmaWx0ZXIoc3RyX2RldGVjdChxdWVyeV9iYXNlcGFpcnMsJ14wK1sxMjVdMCtLJCcpKSAlPiUgI3dlIHdpbGwgaWdub3JlIHF1ZXJpZXMgdGhhdCBhcmUgbm90IHN0YW5kYXJkaXplZCBzaXplcwogIHJlbmFtZShxdWVyeV9icCA9IHF1ZXJ5X2Jhc2VwYWlycykgJT4lCiAgbXV0YXRlKHF1YWxpdHlfaW5jbHVkZWQgPSBUKQpwbGFuKHNlcXVlbnRpYWwpCgpTUkFfdGF4bGFiZWxzID0gc3RyX3JlbW92ZSh2YXJLb2Rlcl9TUkFfcmVzdWx0cyRhY3R1YWxfbGFiZWxzLCI7Kmxvd19xdWFsaXR5OlRydWU7KiIpICU+JSBzdHJfc3BsaXQoJzsnKSAlPiUgdW5saXN0ICU+JSB1bmlxdWUKCnZhcktvZGVyX1NSQV9yZXN1bHRzID0gdmFyS29kZXJfU1JBX3Jlc3VsdHMgJT4lCiAgbXV0YXRlKHF1ZXJ5X2xhYmVscyA9IHN0cl9yZW1vdmUoYWN0dWFsX2xhYmVscywiOypsb3dfcXVhbGl0eTpUcnVlOyoiKSAlPiUgc3RyX3NwbGl0KCc7JykgJT4lIHVubGlzdCwKICAgICAgICAgcHJlZGljdGVkX2xpc3QgPSBzdHJfc3BsaXQocHJlZGljdGVkX2xhYmVscywnOycpCiAgICAgICAgICkgJT4lCiAgcm93d2lzZSgpICU+JQogIG11dGF0ZShmYW1pbHlfY29ycmVjdCA9IHF1ZXJ5X2xhYmVscyAlaW4lIHByZWRpY3RlZF9saXN0LAogICAgICAgICBmYW1pbHlfaW5jb3JyZWN0ID0gaWZlbHNlKGlzLm5hKHByZWRpY3RlZF9sYWJlbHMpLEZBTFNFLGFueSghKHByZWRpY3RlZF9saXN0ICVpbiUgcXVlcnlfbGFiZWxzKSkpKSAlPiUKIHNlbGVjdChtYXRjaGVzKCJeW14wLTldIikpCgp2YXJLb2Rlcl9TUkFfcmVzdWx0cyAKICAgICAgICAgCmBgYAoKTm93IGxldCdzIHN1bW1hcml6ZSBhbmQgcGxvdDoKCmBgYHtyfQpTUkFfc3VtbWFyeV9mYW1pbHkgPSBzdW1tYXJpemVfcmVzdWx0cyh2YXJLb2Rlcl9TUkFfcmVzdWx0cywnZmFtaWx5JykKU1JBX3N1bW1hcnlfZmFtaWx5CgpOX3NhbXAgPSBTUkFfc3VtbWFyeV9mYW1pbHkgJT4lCiBncm91cF9ieShxdWVyeV9icCkgJT4lCiBzdW1tYXJpc2UoTiA9IHN1bShOKSkKCnBfU1JBX2ZhbWlseSA9IHBsb3RfYXJlYShTUkFfc3VtbWFyeV9mYW1pbHksICd2YXJLb2RlciBTUkEgZmFtaWx5JywgcmVsYXRpdmUgPSBUUlVFLHhsaW1fYWxsID0gRkFMU0UpIApwX1NSQV9mYW1pbHkgCmBgYAoKTGV0J3Mgbm93IGRvIHRoZSBTUkEgcGxvdCwgYnV0IHNwbGl0dGluZyBieSBraW5nZG9tLiBGaXJzdCwgd2UgbmVlZCB0byByZXRyaWV2ZSBraW5nZG9tIGluZm9ybWF0aW9uOgpgYGB7cn0KCnBfU1JBX2ZhbWlsaWVzID0gcmVhZF9jc3YoJ2FsbF9TUkEvcnVuc190b19kb3dubG9hZF9kYXRhLmNzdicpICU+JQogIHNlbGVjdChzYW1wbGVfaWQgPSBSdW4sIEtpbmdkb20pICU+JQogIHJpZ2h0X2pvaW4odmFyS29kZXJfU1JBX3Jlc3VsdHMpICU+JQogIHNwbGl0KC4kS2luZ2RvbSkgJT4lCiAgcHVycnI6Om1hcF9kZihzdW1tYXJpemVfcmVzdWx0cywgCiAgICAgICAgICAgICAgICAgbGV2ZWw9J2ZhbWlseScsCiAgICAgICAgICAgICAgICAuaWQ9J0tpbmdkb20nCiAgICAgICAgICAgICAgICApICU+JQogIG11dGF0ZShLaW5nZG9tID0gZmFjdG9yKEtpbmdkb20sbGV2ZWxzPWMoJ01ldGF6b2EnLCdWaXJpZGlwbGFudGFlJywnRnVuZ2knKSxvcmRlcmVkID0gVCkpICU+JQogIHBsb3RfYXJlYShyZWxhdGl2ZT1GQUxTRSx4bGltX2FsbCA9IEZBTFNFLHdyYXAgPSAnfktpbmdkb20nLHRpdGxlPSdGYW1pbGllcyBpbiBTUkEnKSArIGNvb3JkX2NhcnRlc2lhbih4bGltPWMoNTAwLDEwMDAwKSoxMDAwLGV4cGFuZCA9IEZBTFNFKQoKcHJpbnQocF9TUkFfZmFtaWxpZXMpCgoKZ2dzYXZlKCdpbWFnZXNfbWFudXNjcmlwdC9maWczX1NSQV9hY2N1cmFjeS5wZGYnLCB3aWR0aD00LjUsaGVpZ2h0ID0gNCkKZ2dzYXZlKCdpbWFnZXNfbWFudXNjcmlwdC9maWczX1NSQV9hY2N1cmFjeS5wbmcnLCB3aWR0aD00LjUsaGVpZ2h0ID0gNCxkcGkgPSAxMjAwKQpgYGAKCgojIE90aGVyIGRhdGFzZXRzCgpOb3cgd2Ugd2lsbCBtYWtlIGEgc21hbGwgZmlndXJlIHRvIGluY2x1ZGUgdGhlIGFkZGl0aW9uYWwgZGF0YXNldHMgaW4gd2hpY2ggd2UgYXBwbGllZCB2YXJLb2RpbmcuCgpJbiB0aGVzZSBjYXNlcywgd2UgY2hvc2UgYSB0ZXN0IHNldCB0aGF0IGluY2x1ZGVkIGJvdGggdGF4YSBpbiB0aGUgdHJhaW5pbmcgc2V0IGFuZCB0YXhhIG5vdCBpbiB0aGUgdHJhaW5pbmcgc2V0LCBzbyB3ZSB3aWxsIGdyYXBoIGJvdGggc2VwYXJhdGVseS4gVGhpcyBpcyBkZW5vdGVkIGJ5IGEgY29sdW1uIG5hbWVkIGBpbl90cmFpbmluZ19tb2RlbGAuIExldCdzIHN0YXJ0IGJ5IHJlYWRpbmcgcmVzdWx0cy4KCkxldCdzIGRlZmluZSBhIGZ1bmN0aW9uIHRvIHJlYWQgYW5kIHByb2Nlc3MgcHJlZGljdGlvbnM6CgpgYGB7cn0KcmVhZF9hbmRfcHJvY2Vzc19vdGhlcnMgPSBmdW5jdGlvbihpbmZpbGUpewogIAp2YXJrb2Rlcl9yZXN1bHRzID0gcmVhZF9jc3YoaW5maWxlKSAlPiUgCiAgbXV0YXRlKHNhbXBsZV9pZCA9IGFzLmNoYXJhY3RlcihzYW1wbGVfaWQpKSAlPiUKICBzZWxlY3QoLTEpICU+JQogIHJlbmFtZShxdWVyeV9icCA9IHF1ZXJ5X2Jhc2VwYWlycykgCgoKYWxsX3RheGxhYmVscyA9IHN0cl9yZW1vdmUodmFya29kZXJfcmVzdWx0cyRhY3R1YWxfbGFiZWxzLCI7Kmxvd19xdWFsaXR5OlRydWU7KiIpICU+JSBzdHJfc3BsaXQoJzsnKSAlPiUgdW5saXN0ICU+JSB1bmlxdWUKCnZhcmtvZGVyX3Jlc3VsdHMgPSB2YXJrb2Rlcl9yZXN1bHRzICU+JQogIG11dGF0ZShxdWVyeV9sYWJlbHMgPSBzdHJfcmVtb3ZlKGFjdHVhbF9sYWJlbHMsIjsqbG93X3F1YWxpdHk6VHJ1ZTsqIikgJT4lIHN0cl9zcGxpdCgnOycpLAogICAgICAgICBwcmVkaWN0ZWRfbGlzdCA9IHN0cl9zcGxpdChwcmVkaWN0ZWRfbGFiZWxzLCc7JykKICAgICAgICAgKSAlPiUKICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKHRheG9uX2NvcnJlY3QgPSBhbnkocXVlcnlfbGFiZWxzICVpbiUgcHJlZGljdGVkX2xpc3QpLAogICAgICAgICB0YXhvbl9pbmNvcnJlY3QgPSBhbnkoIShwcmVkaWN0ZWRfbGlzdFshaXMubmEocHJlZGljdGVkX2xpc3QpXSAlaW4lIHF1ZXJ5X2xhYmVscykpCiAgICAgICAgICkKCnJldHVybih2YXJrb2Rlcl9yZXN1bHRzKQp9CmBgYAoKTm93IGxldCdzIGFwcGx5IHRoaXMgZnVuY3Rpb24gdG8gYWxsIGZpbGVzLgpgYGB7cn0KcHJlZGljdGlvbl9maWxlcyA9IGxpc3QuZmlsZXMoJ290aGVyX2RhdGFzZXRzJyxwYXR0ZXJuID0gJ3ByZWRpY3Rpb24uK2NzdicsZnVsbC5uYW1lcyA9IFQpCm5hbWVzKHByZWRpY3Rpb25fZmlsZXMpID0gYmFzZW5hbWUocHJlZGljdGlvbl9maWxlcykgJT4lIHN0cl9leHRyYWN0KCIuKig/PV9wcmVkaWN0aW9uX3RhYmxlXFwuY3N2KSIpCgpvdGhlcl9yZXN1bHRzID0gcHVycnI6Om1hcF9kZnIocHJlZGljdGlvbl9maWxlcywgcmVhZF9hbmRfcHJvY2Vzc19vdGhlcnMsIC5pZD0nZGF0YXNldCcpCm90aGVyX3Jlc3VsdHMKYGBgCgpMZXQncyBub3cgc3VtbWFyaXplIGJ5IGRhdGFzZXQgYW5kIHNlcGFyYXRlbHkgZm9yIHRheGEgaW5jbHVkZWQgYW5kIGV4Y2x1ZGVkIGZyb20gdGhlIHRyYWluaW5nIHNldC4KCmBgYHtyfQpzdW1tYXJ5X290aGVycyA9IG90aGVyX3Jlc3VsdHMgJT4lCiAgc3BsaXQoaW50ZXJhY3Rpb24ob3RoZXJfcmVzdWx0cyRkYXRhc2V0LCBvdGhlcl9yZXN1bHRzJGluX3RyYWluaW5nX21vZGVsKSkgJT4lCiAgcHVycnI6Om1hcF9kZnIoc3VtbWFyaXplX3Jlc3VsdHMsIGxldmVsID0gJ3RheG9uJywgLmlkID0gJ2NvbWInKSAlPiUKICBzZXBhcmF0ZShjb21iLCBpbnRvID0gYygiZGF0YXNldCIsICJ0YXhvbl9pbl90cmFpbmluZ19yYXciKSwgc2VwID0gIlxcLiIpICU+JQogIG11dGF0ZSh0YXhvbl9pbl90cmFpbmluZyA9IHRheG9uX2luX3RyYWluaW5nX3JhdyA9PSAneWVzJykgJT4lCiAgc2VsZWN0KC10YXhvbl9pbl90cmFpbmluZ19yYXcpICU+JQogIG11dGF0ZSh0YXhvbl9pbl90cmFpbmluZyA9IGMoJ1RheG9uIG5vdCBpbiB0cmFpbmluZyBzZXQnLCAnVGF4b24gaW4gdHJhaW5pbmcgc2V0JylbdGF4b25faW5fdHJhaW5pbmcrMV0sCiAgICAgICAgIGRhdGFzZXQgPSBzdHJfcmVwbGFjZShkYXRhc2V0LCAiXiguKSIsIH50b3VwcGVyKC54KSkpICU+JQogIG11dGF0ZShyZXN1bHQgPSBmYWN0b3IocmVzdWx0LAogICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzPWMoImNvcnJlY3QiLCAiYW1iaWd1b3VzIiwgImluY29uY2x1c2l2ZSIsICJpbmNvcnJlY3QiKSwKICAgICAgICAgICAgICAgICAgICAgICAgIG9yZGVyZWQ9VCkpCgpzdW1tYXJ5X290aGVycwoKYGBgCk5vdyBsZXQncyBwbG90CmBgYHtyfQpwX290aGVycyA9IGdncGxvdChzdW1tYXJ5X290aGVycyAsIGFlcyh4ID0gZGF0YXNldCwgeSA9IE4sIGZpbGwgPSByZXN1bHQpKSArCiAgZ2VvbV9jb2woKSsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHNldE5hbWVzKFJDb2xvckJyZXdlcjo6YnJld2VyLnBhbCg0LCAiQWNjZW50IiksIGMoImNvcnJlY3QiLCAiYW1iaWd1b3VzIiwgImluY29uY2x1c2l2ZSIsICJpbmNvcnJlY3QiKSkpICsKICAgIHNjYWxlX2FscGhhX21hbnVhbCh2YWx1ZXM9YygwLjUsMSkpICsKICAgIGdndGl0bGUoJ090aGVyIGRhdGFzZXRzJykgKwogICAgeWxhYignTnVtYmVyIG9mIHNhbXBsZXMnKSArCiAgICB0aGVtZV9mZXcoKSArCiAgICAgIHNjYWxlX3lfY29udGludW91cyhtaW5vcl9icmVha3MgPSB3YWl2ZXIoKSkgKwogICAgICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSBOQSksCiAgICAgICAgICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSBncmF5KDAuNSkpLAogICAgICAgICAgICBwYW5lbC5ncmlkLm1pbm9yLnkgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gZ3JheSgwLjYpLGxpbmV0eXBlID0gMiksCiAgICAgICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoZmFjZT0naXRhbGljJyksCiAgICAgICAgICAgIHBhbmVsLm9udG9wID0gVFJVRSkgKwogICAgY29vcmRfY2FydGVzaWFuKGV4cGFuZD1GQUxTRSkgKwogICAgZmFjZXRfZ3JpZCh0YXhvbl9pbl90cmFpbmluZ34uKQogIApwX290aGVycwoKZ2dzYXZlKCdpbWFnZXNfbWFudXNjcmlwdC9maWczX290aGVyc19hY2N1cmFjeS5wZGYnLCB3aWR0aD00LjUsaGVpZ2h0ID0gNCkKZ2dzYXZlKCdpbWFnZXNfbWFudXNjcmlwdC9maWczX290aGVyc19hY2N1cmFjeS5wbmcnLCB3aWR0aD00LjUsaGVpZ2h0ID0gNCxkcGkgPSAxMjAwKQoKYGBgCgoKCgojIEdlbmVyYXRpbmcgbnVtYmVycyBmb3IgcHVibGljYXRpb24KCkhlcmUgd2UganVzdCBxdWVyeSBvdXIgcmVzdWx0cyB0byBnZXQgYSBmZXcgZmlndXJlcyB0aGF0IHdlIHJlcG9ydCBpbiB0aGUgcGFwZXIuCgpUb3RhbCBudW1iZXIgb2Ygc2FtcGxlcyB1c2VkIGluIGNyb3NzLXZhbGlkYXRpb246CmBgYHtyfQpkaW0oc2FtcF9sYWJlbHMpCmBgYAoKTnVtYmVyIG9mIFN0aWdtYXBoeWxsb24gc2FtcGxlcyB3aXRoIGVhY2gga2luZCBvZiBlcnJvciBmb3IgdmFya29kZXI6CmBgYHtyfQpzdW1tYXJ5X3NwZWNpZXMKYGBgCgpOdW1iZXIgb2YgU3RpZ21hcGh5bGxvbiBzYW1wbGVzIHdpdGggZWFjaCBraW5kIG9mIGVycm9yIGZvciBza21lcjoKYGBge3J9CnNrbWVyX3N1bW1hcnlfc3BlY2llcwpgYGAKVHJhZGl0aW9uYWwgYmFyY29kZSBhY2N1cmFjeSBmb3Igc3BlY2llczoKYGBge3J9CmJhcmNvZGVfc3VtbWFyeV9zcGVjaWVzICU+JSBhcnJhbmdlKHF1ZXJ5X2JwLG1hcmtlcikKYGBgCkNvbmNhdGVuYXRlZCBiYXJjb2RlIGFjY3VyYWN0IGZvciBzcGVjaWVzOgpgYGB7cn0KY29uY2F0X3N1bW1hcnlfc3BlY2llcwpgYGAKCgoKdmFyS29kZXIgYWNjdXJhY3kgZm9yIGdlbmVyYToKYGBge3J9CnN1bW1hcnlfZ2VudXMKYGBgCnZhcktvZGVyIGFjY3VyYWN5IGZvciBmYW1pbHk6CmBgYHtyfQpzdW1tYXJ5X2ZhbWlseQpgYGAKCgoKU2ttZXIgYWNjdXJhY3kgZm9yIGdlbmVyYToKYGBge3J9CnNrbWVyX3N1bW1hcnlfZ2VudXMKYGBgCgpTa21lciBhY2N1cmFjeSBmb3IgZmFtaWx5OgpgYGB7cn0Kc2ttZXJfc3VtbWFyeV9mYW1pbHkKYGBgCgoKTnVtYmVyIG9mIHNhbXBsZXMgYXZhaWxhYmxlIGZvciBlYWNoIGdlbnVzIGFuZCBkYXRhIGFtb3VudApgYGB7cn0KcmVzdWx0cyAlPiUKICBtdXRhdGUoZ2VudXMgPSBzdHJfZXh0cmFjdChhY3R1YWxfbGFiZWxzLCIoPzw9Z2VudXM6KVteO10rIikpICU+JQogIGdyb3VwX2J5KHF1ZXJ5X2JwKSAlPiUKICBzdW1tYXJpemUoTj1uKCkpICU+JQogIGNvbXBsZXRlKCkKYGBgClBsb3QgbnVtYmVyIG9mIHNhbXBsZXMgZm9yIHN1cHBsZW1lbnRhcnkgbWF0ZXJpYWwuCgpgYGB7cn0Kbl9zYW1wbGVzX2dlbmVyYSA9IHJlc3VsdHMgJT4lCiAgbXV0YXRlKHRheG9uID0gc3RyX2V4dHJhY3QoYWN0dWFsX2xhYmVscywiKD88PWdlbnVzOilbXjtdKyIpKSAlPiUKICBncm91cF9ieSh0YXhvbiwgcXVlcnlfYnApICU+JQogIHN1bW1hcml6ZShOPW4oKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIGNvbXBsZXRlKHRheG9uLCBxdWVyeV9icCwgZmlsbCA9IGxpc3QoTj0wKSkgJT4lCiAgbXV0YXRlKHRheG9uID0gZmN0X3Jlb3JkZXIodGF4b24sIE4pKQpuX3NhbXBsZXNfZ2VuZXJhIAoKbl9zYW1wbGVzX3NwZWNpZXMgPSByZXN1bHRzICU+JQogIG11dGF0ZSh0YXhvbiA9IHN0cl9leHRyYWN0KGFjdHVhbF9sYWJlbHMsIig/PD1zcGVjaWVzOilbXjtdKyIpKSAlPiUKICBmaWx0ZXIoIWlzLm5hKHRheG9uKSkgJT4lCiAgZ3JvdXBfYnkodGF4b24sIHF1ZXJ5X2JwKSAlPiUKICBzdW1tYXJpemUoTj1uKCkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBjb21wbGV0ZSh0YXhvbiwgcXVlcnlfYnAsIGZpbGwgPSBsaXN0KE49MCkpICAlPiUKICBtdXRhdGUodGF4b24gPSBmY3RfcmVvcmRlcih0YXhvbiwgTikpCm5fc2FtcGxlc19zcGVjaWVzIApgYGAKRm9yIFNSQSwgd2UgaGF2ZSB0byBjb3VudCBib3RoIHZhbGlkYXRpb24gYW5kIHRyYWluaW5nIHNhbXBsZXMsIHNpbmNlIHdlIGRpZCBub3QgZG8gY3Jvc3MtdmFsaWRhdGlvbi4gTGV0J3MgdXNlIGltYWdlIG5hbWVzIHRvIGdldCB0aGUgaW5mb3JtYXRpb24gYW5kIHRoZW4gdGhlIHJlc3VsdHMgdGFibGUgdG8gZmlndXJlIG91dCB3aGljaCBvbmVzIHdlcmUgaW4gdGhlIHZhbGlkYXRpb24gc2V0LgpgYGB7cn0KYWxsX2ZpbGVzID0gYyhsaXN0LmZpbGVzKCdhbGxfU1JBL3ZhcmtvZGVyX2ltYWdlc19TUkEvJyxwYXR0ZXJuPScqLnBuZycscmVjdXJzaXZlID0gVCksCiAgICAgICAgICAgICAgbGlzdC5maWxlcygnYWxsX1NSQS92YXJrb2Rlcl9xdWVyeV9pbWFnZXMvJyxwYXR0ZXJuPScqLnBuZycscmVjdXJzaXZlID0gVCkpCgpuX3NhbXBsZXNfU1JBID0gZGF0YS5mcmFtZShmaWxlbmFtZT1hbGxfZmlsZXMpICU+JQogIG11dGF0ZSgKICAgIHNhbXBsZV9pZCA9IHN0cl9leHRyYWN0KGZpbGVuYW1lLCAiXiguKykoPz1AKSIpLCAgIyBDYXB0dXJlIGV2ZXJ5dGhpbmcgdXAgdG8gdGhlICJAIiBzeW1ib2wgYnV0IGV4Y2x1ZGUgdGhlIHN5bWJvbCBpdHNlbGYKICAgIHF1ZXJ5X2JwID0gc3RyX2V4dHJhY3QoZmlsZW5hbWUsICIoPzw9QCkoWzAtOV0rKUsiKSAjIG11bHRpcGx5IGJ5IDEwMDAgdG8gY29udmVydCBLIHRvIHRoZSBhY3R1YWwgbnVtYmVyCiAgKSAlPiUgCiAgbGVmdF9qb2luKHJlYWRfY3N2KCdhbGxfU1JBL3J1bnNfdG9fZG93bmxvYWRfZGF0YS5jc3YnKSAlPiUgCiAgICAgICAgICAgICAgc2VsZWN0KHNhbXBsZV9pZD1SdW4sS2luZ2RvbSx0YXhvbj1GYW1pbHlJRCkpICU+JQogIG11dGF0ZV9hdCh2YXJzKHRheG9uKSxhcy5jaGFyYWN0ZXIpICU+JQogIG11dGF0ZSh2YWxpZGF0aW9uX3NldCA9IHNhbXBsZV9pZCAlaW4lIHZhcktvZGVyX1NSQV9yZXN1bHRzJHNhbXBsZV9pZCkgJT4lCiAgZ3JvdXBfYnkodGF4b24sIHF1ZXJ5X2JwLHZhbGlkYXRpb25fc2V0KSAlPiUKICBzdW1tYXJpemUoTj1uKCkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBtdXRhdGUodGF4b24gPSBmY3RfcmVvcmRlcih0YXhvbiwgTikpCgpuX3NhbXBsZXNfU1JBCiAgCgogICgobGlzdC5maWxlcygnYWxsX1NSQS92YXJrb2Rlcl9pbWFnZXNfU1JBLycscGF0dGVybj0nKi5wbmcnLHJlY3Vyc2l2ZSA9IFQpICU+JQogICAgICBzdHJfZXh0cmFjdCgiXiguKykoPz1AKSIpKSVpbiUKdmFyS29kZXJfU1JBX3Jlc3VsdHMkc2FtcGxlX2lkKSAlPiUgc3VtbWFyeQpgYGAKCgoKYGBge3J9CnBsb3RfTnNhbXBsZXNfYXJlYSA9IGZ1bmN0aW9uKGRmLCB0aXRsZSl7CiAgZGYgPSBkZiAlPiUgCiAgICBtdXRhdGUocXVlcnlfYnAgPSBwYXJzZV9udW1iZXIocXVlcnlfYnApICoxMDAwKQogIAogIG5fbGV2ZWxzIDwtIGxlbmd0aCh1bmlxdWUoZGYkdGF4b24pKQogIHZpcmlkaXNfY29sb3JzIDwtIHZpcmlkaXM6OnR1cmJvKG5fbGV2ZWxzKQogIAogIGhhbGZfbiA8LSBjZWlsaW5nKG5fbGV2ZWxzIC8gMikKICByZW9yZGVyZWRfY29sb3JzIDwtIGMocmJpbmQodmlyaWRpc19jb2xvcnNbMTpoYWxmX25dLCB2aXJpZGlzX2NvbG9yc1soaGFsZl9uICsgMSk6bl9sZXZlbHNdKSkKCgogIAogIAogIGdncGxvdChkZiwgYWVzKHg9cXVlcnlfYnAseT1OLGZpbGw9dGF4b24sIGNvbG9yID0gdGF4b24sIGdyb3VwID0gdGF4b24pKSArCiAgICBnZW9tX2FyZWEocG9zaXRpb249IHBvc2l0aW9uX3N0YWNrKCkpICsKICAgICNnZW9tX2xpbmUocG9zaXRpb249J3N0YWNrJykgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gcmVvcmRlcmVkX2NvbG9ycywgCiAgICAgICAgICAgICAgICAgICAgICBhZXN0aGV0aWNzID0gYygnY29sb3VyJywnZmlsbCcpLAogICAgICAgICAgICAgICAgICAgICAgZ3VpZGUgPSAnbm9uZScpICsKICAgIHNjYWxlX3hfbG9nMTAobGFiZWxzID0gc2NhbGVzOjpsYWJlbF9udW1iZXIoc2NhbGVfY3V0ID0gc2NhbGVzOjpjdXRfc2koJ2JwJykpLAogICAgICAgICAgICAgICAgICBicmVha3MgPSAxMDAwKnBhcnNlX251bWJlcih1bmlxdWUobl9zYW1wbGVzX2dlbmVyYSRxdWVyeV9icCkpLAogICAgICAgICAgICAgICAgICBsaW1pdHMgPSAxMDAwKnJhbmdlKHBhcnNlX251bWJlcih1bmlxdWUobl9zYW1wbGVzX2dlbmVyYSRxdWVyeV9icCkpKSkgICsKICAgIHNjYWxlX3lfY29udGludW91cyhuLmJyZWFrcyA9IDEwLCBtaW5vcl9icmVha3MgPSB3YWl2ZXIoKSkgKwogICAgZ2d0aXRsZSh0aXRsZSkgKwogICAgeWxhYignTnVtYmVyIG9mIHNhbXBsZXMnKSArCiAgICB4bGFiKCdCYXNlIHBhaXJzIGluIHF1ZXJ5IGltYWdlcycpICsKICAgIHRoZW1lX2ZldygpICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGhqdXN0PTEsYW5nbGU9NDUpLAogICAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gTkEpLAogICAgICAgICAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gZ3JheSgwLjUpKSwKICAgICAgICAgICAgcGFuZWwuZ3JpZC5taW5vci55ID0gZWxlbWVudF9saW5lKGNvbG91ciA9IGdyYXkoMC42KSxsaW5ldHlwZSA9IDIpLAogICAgICAgICAgICBwYW5lbC5vbnRvcCA9IFRSVUUpCn0KYGBgCgpgYGB7cn0KTl9zcGVjaWVzID0gcGxvdF9Oc2FtcGxlc19hcmVhKG5fc2FtcGxlc19zcGVjaWVzLHRpdGxlPSdTdGlnbWFwaHlsbG9uIFNwZWNpZXMnKQpOX2dlbmVyYSA9IHBsb3RfTnNhbXBsZXNfYXJlYShuX3NhbXBsZXNfZ2VuZXJhLHRpdGxlPSdNYXBsaWdoaWFsZXMgR2VuZXJhJykKTl9mYW1pbGllcyA9IHBsb3RfTnNhbXBsZXNfYXJlYShuX3NhbXBsZXNfU1JBLHRpdGxlPSdFdWthcnlvdGljIGZhbWlsZXMnKSArIGZhY2V0X3dyYXAofnZhbGlkYXRpb25fc2V0KQoKcCA9IGNvd3Bsb3Q6OnBsb3RfZ3JpZChOX2dlbmVyYSxOX3NwZWNpZXMsTl9mYW1pbGllcywgbnJvdyA9IDEpCgpwcmludChwKQoKZ2dzYXZlKCdpbWFnZXNfbWFudXNjcmlwdC9zdXBwX2ZpZ19uX3NhbXBsZXMucGRmJywgd2lkdGg9OCxoZWlnaHQgPSA0KQpnZ3NhdmUoJ2ltYWdlc19tYW51c2NyaXB0L3N1cHBfZmlnX25fc2FtcGxlcy5wbmcnLCB3aWR0aD04LGhlaWdodCA9IDQsZHBpID0gMTIwMCkKCmBgYAoKVG90YWwgbnVtYmVyIG9mIFNSQSBzYW1wbGVzLiBWYWxpZGF0aW9uOgpgYGB7cn0KcmVhZF9jc3YoJ3ZhcktvZGVyL2FsbF9TUkEvdmFya29kZXJfdHJhaW5lZF9tb2RlbF9NTC9pbnB1dF9kYXRhLmNzdicpWy0xXSAlPiUKICBncm91cF9ieShpc192YWxpZCkgJT4lCiAgc3VtbWFyaXNlKE4gPSBuKCkpCmBgYAoKCgoK